Search code examples
angularjasmineangular-servicesangular-components

How to use jasmineSpy to test Angular component?


I have component and service like below. I want to write a unit test to test this component, trying to use a Spy to emulate the service, however it throws "no provider for HttpClient" error on the initiating the component, because my NewsService is dependant on HttpClient.

How should I adjust my code to inject the HttpClient?

HomeComponent:

import { Component,Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';
import { News } from '../models/news';
import { NewsService } from '../service/news.service';

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
providers: [NewsService]
})
export class HomeComponent {
public news: News[];
public headline: News;
private baseUrl: string;
constructor(private router: Router, private service: NewsService) {

}
ngOnInit() {
  let baseUrl = document.getElementsByTagName('base')[0].href;
  var headurl = this.baseUrl + "api/News/Headline?country=us&category=business";
  this.service.getNews(headurl).subscribe((res) => { this.headline = res[0]; });

  var url = this.baseUrl + "api/News/Category?country=us&category=business";

  this.service.getNews(url).subscribe((res) => this.news = res);

}

NewsService

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';
import { News } from '../models/news';
import { Observable } from 'rxjs';

const httpOptions = {
headers: new HttpHeaders({
'Content-Type':  'application/json'
})
};

@Injectable()
export class NewsService {
constructor(
private http: HttpClient) {
}

getNews(url:string): Observable<News[]> {
  return this.http.get<News[]>(url);
  }

}

Test

import { async, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { News } from '../models/news';
import { NewsService } from '../service/news.service';
import { HomeComponent } from '../home/home.component';
import { of } from 'rxjs/observable/of';import { Router } from '@angular/router';

describe('HomeComponent', () => {
        let component: HomeComponent;
        let fixture: ComponentFixture < HomeComponent > ;
        let getNewsSpy: jasmine.Spy;
        let headlineEl: HTMLElement;
        let testheadline: News[];
        let url: string;

        beforeEach(() => {
            testheadline = [{
                NewsId: 'test',
                author: 'test',
                description: 'headline',
                publishedAt: '2018-8-8',
                title: 'Android ',
                url: 'http://www.ghostchina.com',
                urlToImage: 'http://static.ghostchina.com/image/c/06/765c76cb1ca259dd8fe8002459bbc.jpg'
            }] as News[];

            const newsService = jasmine.createSpyObj('NewsService', ['getNews']);
            let url = 'api/News/Headline?country=us&category=business';
            getNewsSpy = newsService.getNews.and.returnValue( of (testheadline));
            const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

            TestBed.configureTestingModule({
                declarations: [HomeComponent],
                providers: [{
                    provide: NewsService,
                    useValue: newsService
                }, {
                    provide: Router,
                    useValue: routerSpy
                }]
            });
            fixture = TestBed.createComponent(HomeComponent);
            component = fixture.componentInstance;
        });

        describe('#oninit', () => {
                    it('should return expected category news (called once)', () => {
                                let url = 'api/News/Category?country=us&category=business';
                                fixture.detectChanges(); // onInit()  
                                expect(getNewsSpy.calls.any()).toBe(false, 'getNews not yet called');         });  });

Solution

  • You get the error because of this.

    @Component({
        providers: [NewsService]
    })
    

    When we provide a service in the component level using @component.providers. It takes precedence over any global providers, this makes the provider scoped only to the component.

    So no point of providing a stub service to the testing module.(As you have a component level service.)

    What you have to do is provide the service stub to the component. To do that you can use TestBed.overrideComponent method. Using that we can override the component's templates, and providers.

     const newsService = jasmine.createSpyObj('NewsService', ['getNews']);
    
     TestBed.configureTestingModule({
       declarations: [HomeComponent]
     });
    
     TestBed.overrideComponent(HomeComponent, {
       set: {
        providers: [
                    {provide: NewsService,useValue: newsService}
        ]
      }
    })
    

    And you can read more about Overriding a component's providers in Angular Testing Docs