In this tutorial, you’ll see how to unit test Angular component with service or how to mock service dependency while testing an Angular component method.

Have a look at the YouTube video if you prefer a video tutorial else keep scrolling.

How To Unit Test Angular Component With Service

For the sake of this tutorial, I created a fresh Angular project using Angular CLI. I added some code to fetch some data from an API using an Angular service. So, here is how the modified app.component.ts file :


import { Component, OnInit } from '@angular/core';
import { AppService } from './app.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  postDetails;
  showLoadingIndicator;

  constructor(private service : AppService){
    this.postDetails = [];
  }

  ngOnInit(){
    this.getPostDetails();
  }

  getPostDetails(){
    this.showLoadingIndicator = true;
    this.service.getPosts().subscribe((response : []) => {
      this.showLoadingIndicator = false;
      if(response && response.length > 0) {
        this.postDetails = response;
      } else {
        this.postDetails = [];
      }
    })
  }
}

Here is the app.service.ts file :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class AppService {

  constructor(private http : HttpClient) { }

  getPosts(){
    return this.http.get('https://jsonplaceholder.typicode.com/posts')
  }
}

Mock Service Dependency In Angular

Let’s write unit test for testing the method getPostDetails which calls service to get data. For unit testing the method, you need to mock the service method getPosts to test the component method.

Let’s start by writing unit test for testing method getPostDetails when the response from service method getPosts is an empty array.

Add the following unit test case to the app.component.spec.ts file.

  it('should call getPostDetails and get response as empty array', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    const service = fixture.debugElement.injector.get(AppService);
    let spy_getPosts = spyOn(service,"getPosts").and.callFake(() => {
      return Rx.of([]).pipe(delay(100));
    });
    component.getPostDetails();
    tick(100);
    expect(component.postDetails).toEqual([]);
  })) 

As seen in the above code, I have used spyOn to spy on the service call for method getPosts and fake response as an empty array with a delay of 100ms.

Let’s add another unit test case to test another scenario when there is data returned from service getPosts method.

   it('should call getPostDetails and get response as array', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    const service = fixture.debugElement.injector.get(AppService);
    let spy_getPosts = spyOn(service,"getPosts").and.callFake(() => {
      return Rx.of([{postId : 100}]).pipe(delay(2000));
    });
    component.getPostDetails();
    tick(1000);
    expect(component.showLoadingIndicator).toEqual(true);
    tick(1000);
    expect(component.showLoadingIndicator).toEqual(false);
    expect(component.postDetails).toEqual([{postId : 100}]);
  })) 

I have modified the return data from the service mock spy_getPosts to return [{postId : 100}]. As seen in the mock spy_getPosts I have added a delay of 2000 ms. After making the component method call getPostDetails(), I simulated the asynchronous passage of 1000 ms using tick method. Since, the service call is yet to be completed, the showingLoadingIndicator should be true. Then I simulated another 1000 ms of time which completed the service call. At this point, the showingLoadingIndicator should be false. I also expected the postDetails array to be equal to fake array response [{postId : 100}].

Here is how the complete app.component.spec.ts file looks :

import { TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from "@angular/common/http/testing";
import {  AppComponent } from './app.component';
import { AppService } from './app.service';
import * as Rx from 'rxjs';
import { delay } from "rxjs/operators";


describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ],
      declarations: [
        AppComponent
      ],
      providers : [
        AppService
      ]
    }).compileComponents();
  }));

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    expect(component).toBeTruthy();
  });

  it('should call ngOnInit', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    let spy_getPostDetails = spyOn(component,"getPostDetails").and.returnValue([]);
    component.ngOnInit();
    expect(component.postDetails).toEqual([]);
  })

  it('should call getPostDetails and get response as empty array', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    const service = fixture.debugElement.injector.get(AppService);
    let spy_getPosts = spyOn(service,"getPosts").and.callFake(() => {
      return Rx.of([]).pipe(delay(100));
    });
    component.getPostDetails();
    tick(100);
    expect(component.postDetails).toEqual([]);
  }))

  it('should call getPostDetails and get response as array', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.debugElement.componentInstance;
    const service = fixture.debugElement.injector.get(AppService);
    let spy_getPosts = spyOn(service,"getPosts").and.callFake(() => {
      return Rx.of([{postId : 100}]).pipe(delay(2000));
    });
    component.getPostDetails();
    tick(1000);
    expect(component.showLoadingIndicator).toEqual(true);
    tick(1000);
    expect(component.showLoadingIndicator).toEqual(false);
    expect(component.postDetails).toEqual([{postId : 100}]);
  }))
  
});

Save the above changes and try running the above unit test cases using the following command:

ng test --code-coverage

Wrapping It Up

In this tutorial, you learnt how to unit test an Angular component with service call. I used spyOn to spy on an Angular service call and return fake response using callFake.

Do follow up for other Angular unit testing quick tips and tutorial in the coming weeks.