Angular 9 testing component dependant on service with observable - Cannot read property 'subscribe' of undefined

Viewed 567

I am deperately trying to test a component that is using a service. I use spy to mock it but every time I run tests it fails with exception:

Cannot read property 'subscribe' of undefined

My test looks like this:

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

  let filmsServiceSpy: jasmine.SpyObj<FilmsService>;

  beforeEach(async(() => {
    const spy = jasmine.createSpyObj('FilmsService', 
    ['searchFilmByID']
    );

    TestBed.configureTestingModule({
      declarations: [ FilmOverviewComponent ],
      providers: [
        {provide: AppTitleService, useValue: {getTitle: () => 'title'}},
        {provide: ActivatedRoute, useValue: {params: of({id: 123})} },
        {provide: FilmsService, useValue: spy}
      ]
    })
    .compileComponents();

    filmsServiceSpy = TestBed.get(FilmsService);
  }));

  beforeEach(() => {
    filmsServiceSpy.searchFilmByID.and.returnValue(Observable.create([{title: "", year: ""}]));
    fixture = TestBed.createComponent(FilmOverviewComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

Methods used in the service:

searchFilmByID(movieID: string): Observable<Film> {
    return this.http.get<Film>(this.getUrlWithID(movieID));
  }

  private getUrlWithID(movieID: string) {
    return 'api/externalfilms/film/' + movieID;
  }

I have no idea how to tackle this. I suspect that it would be resolved with some kind of mocking of subscribe method but I completely failed at that.

Thank you for your help in advance!

1 Answers

The error is from this.http.get<Film>.... There are 2 ways to solve it.

First way - mock http client service and call fake

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

  let filmsService: FilmsService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FilmOverviewComponent ],
      imports: [ HttpClientTestingModule ],
      providers: [
        {provide: AppTitleService, useValue: {getTitle: () => 'title'}},
        {provide: ActivatedRoute, useValue: {params: of({id: 123})} },
        FilmsService
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    filmsService = TestBed.inject(FilmsService);

    /* Mock response */
    const httpClient: HttpClient = TestBed.inject(HttpClient);
    spyOn(httpClient, 'get').and.callFake(() => of({title: "", year: ""}));

    fixture = TestBed.createComponent(FilmOverviewComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

Second way - call fake service method

const FilmsServiceStub = jasmine.createSpyObj('FilmsService', ['searchFilmByID']);

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

  let filmsService: FilmsService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ FilmOverviewComponent ],
      providers: [
        {provide: AppTitleService, useValue: {getTitle: () => 'title'}},
        {provide: ActivatedRoute, useValue: {params: of({id: 123})} },
        {provide: FilmsService, useValue: FilmsServiceStub}
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    filmsService = TestBed.inject(FilmsService);

    /* Mock response */
    spyOn(filmsService, 'searchFilmByID').and.callFake(() => of({title: "", year: ""}));

    fixture = TestBed.createComponent(FilmOverviewComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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