0
votes

I'm stuck on this again and although this thread (MockService still causes error: Cannot read property 'subscribe' of undefined) is exactly about the same thing it still doesn't resolve my problem.

I do have a component which calls a simple function on ngOnInit:

ngOnInit() {
  this.getModules();
}

getModules() {
  this.configService.getModulesToDisplay().subscribe(modules => {
    this.modulesToDisplay = modules;
    }
  );
}

I want to test two things:

Is getModules called on ngOnInit

and

Is this.modulesToDisplayed reassigned when it gets some result

So I mocked my service but the first test still fails with the TypeError 'Cannot read property 'subscribe' of undefined'.

I moved my mocked Service to all different areas since I do guess the mock isn't available when the test starts to build the component. But I still couldn't make it out. My Test looks like:

describe('NavigationComponent', () => {
  let component: NavigationComponent;
  let fixture: ComponentFixture<NavigationComponent>;
  let configServiceMock: any;

  beforeEach(async(() => {

    configServiceMock = jasmine.createSpyObj('ConfigService', ['getModulesToDisplay']);
    configServiceMock.getModulesToDisplay.and.returnValue( of(['module1', 'module2']) );

    TestBed.configureTestingModule({
      declarations: [
        NavigationComponent
      ],
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      providers: [
        { provide: ConfigService, useValue: configServiceMock },
      ],
      schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
    }).compileComponents();

  beforeEach(() => {
    // configServiceMock.getModulesToDisplay.and.returnValue( of(['module1', 'module2']) );
    fixture = TestBed.createComponent(NavigationComponent);
    component = fixture.componentInstance;
  });
  }));

I removed fixture.detectChanges() to have full control over when ngOnInit should be called and so my tests look like:

    it('should call getModulesToDisplay one time on ngOninit()', () => {
      const spyGetModules = spyOn(component, 'getModules');
      component.ngOnInit();
      expect(spyGetModules).toHaveBeenCalledTimes(1);
    });

The first test fails with the Cannot read subscribe error. But the second one passes with the correct mockvalue being used.

  it('should assign result to modulesToDisplay', () => {
    component.getModules();
    expect(component.modulesToDisplay.length).toBeGreaterThan(0);
  });

Any hints on what am I still missing are highly appreciated!

1
"Is getModules called on ngOnInit" is not something you want to test. That's an internal detail of the component, getModules could probably be private, you want to test that it interacts correctly with the test double of the collaborator. Don't call the method directly, only invoke it via ngOnInit, so you have the freedom to refactor within the class.jonrsharpe
Thanks for your answer. I do get your point and will delete the ngOnInit Test. Although I'm still wondering why it works in the example of the link I've posted in my question but not in my setup. I's still love to know if the returned value of a mocked service is already available when the test compiles the Component.BreadcrumbPie
@BreadcrumbPie : Did u try my reusable approach ? I see a downvote. Do let me know if you find this irrelevant and I'll delete thisShashank Vivek
Sorry for my delayed answer. I fiddled around a while with all different kind of tests (I've seen your blog before, too ;) ) . I think my biggest problem is to find out what test ist really needed. So I decided not to go with the ngOninit Tests at allBreadcrumbPie

1 Answers

-2
votes

Rather than writing jasmine spy in each spec file, create a reusable Mock file

export class MockConfigService{
   getModulesToDisplay(){
     return of({
         // whatever module object structure is
     })
   }
}

and in it block:

    it('should call getModulesToDisplay one time on ngOninit()', () => {
      spyOn(component, 'getModules').and.callThrough();
      component.ngOnInit();
      expect(component.getModules).toHaveBeenCalledTimes(1);
    });