Mike Sav
Mike Sav

Reputation: 15321

Angular 5 / Jasmine Unit Test - Cannot read property 'componentInstance' of undefined

I have a strange error with my unit tests and for some reason I cannot determine why I am getting this error. U have only two tests. One to make sure the component is created and the other to check that when a component method is called it calls the Mat Dialog .open method... nothing too complex. Here is my code...

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);
    });
  });
});

The first test passes as expected however the second test fails giving the following error:

TypeError: Cannot read property 'componentInstance' of undefined

I have looked at many answers here and what I have applied to try and fix this hasn't worked. I am sure it is something to do with how the testbed is loaded but I can't determine what is wrong and why?

Upvotes: 2

Views: 14255

Answers (1)

Mike Sav
Mike Sav

Reputation: 15321

I found out the problem. The error message Cannot read property 'componentInstance' of undefined made me focus on the

component = fixture.componentInstance;

This was not the case, the mock of my Modal, spyMatDialog, was incorrecly mocked! The code below shows how the spyMatDialog was correctly mocked:

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();
    });
  });
});

Upvotes: 2

Related Questions