首页 > 解决方案 > Angular 5 / Jasmine 单元测试 - 无法读取未定义的属性“componentInstance”

问题描述

我的单元测试有一个奇怪的错误,由于某种原因,我无法确定为什么会出现这个错误。你只有两个测试。一个是确保组件已创建,另一个是检查组件方法是否在调用时调用 Mat Dialog .open 方法......没什么太复杂的。这是我的代码...

describe('ClientDeactivateComponent', () => {
  let component: ClientDeactivateComponent;
  let fixture: ComponentFixture<ClientDeactivateComponent>;

  const spyMatDialog = jasmine.createSpyObj('MatDialog', ['open']);
  const spyRouter = jasmine.createSpyObj('Router', ['navigate']);
  spyRouter.navigate.and.returnValue(Promise.resolve({}));

  const spyClientsService = jasmine.createSpyObj('ClientsService', ['deactivateClient']);
  const successDeativation = {};

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ClientDeactivateComponent,
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      imports: [
        HttpClientModule,
        RouterTestingModule.withRoutes([{path: '**', component: ClientDeactivateComponent}])
      ],
      providers: [
        FormBuilder,
        ClientsService
      ]
    }).overrideComponent(ClientDeactivateComponent, {
        set: {
          providers: [
            {provide: Router, useValue: spyRouter},
            {provide: ClientsService, useValue: spyClientsService},
            {provide: MatDialog, useValue: spyMatDialog},
            {provide: Router, useValue: spyRouter},
            {provide: ActivatedRoute, useValue: {params: from([{id: 1}])}}
          ],
          template: '<div>Overridden template</div>'
        }
      }
    )
      .compileComponents();
  }));

  afterEach(() => {
    spyMatDialog.open.calls.reset();
    spyRouter.navigate.calls.reset();
    spyClientsService.deactivateClient.calls.reset();
    fixture.detectChanges();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ClientDeactivateComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  describe('default', () => {
    /* this works! */
    it('should create', () => {
      expect(component).toBeTruthy();
    });

    /* Why isn't this working? */
    it('should open a modal window when confirmDeactivation is called', () => {
      component.confirmDeactivation();
      spyClientsService.deactivateClient.and.returnValue(successDeativation);
      expect(spyMatDialog.open).toHaveBeenCalledWith(ConfirmDialogComponent);
    });
  });
});

第一个测试按预期通过,但第二个测试失败,出现以下错误:

TypeError:无法读取未定义的属性“componentInstance”

我在这里查看了很多答案,但我申请尝试解决这个问题的方法并没有奏效。我确信这与测试平台的加载方式有关,但我无法确定出了什么问题,为什么?

标签: angularjasminekarma-jasmine

解决方案


我发现了问题。错误消息无法读取未定义的属性“componentInstance”使我专注于

component = fixture.componentInstance;

事实并非如此,我的模态模型 spyMatDialog 的模拟被错误地模拟了!下面的代码显示了如何正确模拟 spyMatDialog:

describe('ClientDeactivateComponent', () => {
  let component: ClientDeactivateComponent;
  let fixture: ComponentFixture<ClientDeactivateComponent>;

  let spyDialogRef: any;

  const spyRouter = jasmine.createSpyObj('Router', ['navigate']);
  spyRouter.navigate.and.returnValue(Promise.resolve({}));

  const spyClientsService = jasmine.createSpyObj('ClientsService', ['deactivateClient']);
  spyClientsService.deactivateClient = () => of(true);

  spyDialogRef = jasmine.createSpy();
  spyDialogRef.componentInstance = {title: '', message: ''};
  spyDialogRef.afterClosed = () => of(true);

  const spyMatDialog = jasmine.createSpyObj('MatDialog', ['open']);
  spyMatDialog.open.and.returnValue(spyDialogRef);

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ClientDeactivateComponent,
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      imports: [
        HttpClientModule,
        RouterTestingModule.withRoutes([{path: '**', component: ClientDeactivateComponent}])
      ],
      providers: [
        FormBuilder,
        ClientsService
      ]
    }).overrideComponent(ClientDeactivateComponent, {
        set: {
          providers: [
            {provide: Router, useValue: spyRouter},
            {provide: ClientsService, useValue: spyClientsService},
            {provide: MatDialog, useValue: spyMatDialog},
            {provide: Router, useValue: spyRouter},
            {provide: ActivatedRoute, useValue: {params: from([{id: 1}])}}
          ],
          template: '<div>Overridden template</div>'
        }
      }
    )
      .compileComponents();
  }));

  afterEach(() => {
    spyRouter.navigate.calls.reset();
    fixture.detectChanges();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ClientDeactivateComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  describe('default', () => {
    it('should create', () => {
      expect(component).toBeTruthy();
    });

    it('should open a modal window when confirmDeactivation is called', () => {
      component.confirmDeactivation();
      expect(spyMatDialog.open).toHaveBeenCalled();
    });
  });
});

推荐阅读