How to write unit testing for Angular Service classes and httpclient dependencies Angular


In this tutorial, you learn how to unit test angular services with example test cases. It also includes following things

  • How to Setup a unit test Angular service
  • Unit test service functions
  • Mock dependencies with jasmine library
  • Assert expected and actual with karma library

Unit testing is an testing an piece of code with run as expected.

In my previous post, We discuss about complete tutorials on Angular service with examples

We have a service contains methods of reusable business methods which are independent

  • aschronous functions
  • synchronous functions
  • Services methods with dependencies

we have to write a unit test code to test the service independently.

In my previous article, Discussed about how to create and write a Angular service

I am assuming that you have an angular app already created.

Let’s create an angular service using below command

ng g service employee

here is an output

B:\angular-app-testing>ng g service employee
CREATE src/app/employee.service.spec.ts (347 bytes)
CREATE src/app/employee.service.ts (133 bytes)

Here is an service code that we use for writing test cases employee.service.ts:

import { Injectable } from '@angular/core';

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

  constructor() { }
}

Here is Service spec file for Angular service code employee.service.spec.ts:

import { TestBed } from '@angular/core/testing';

import { EmployeeService } from './employee.service';

describe('EmployeeService', () => {
  let service: EmployeeService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(EmployeeService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

Consider this an employee Service which has Service methods for

  • Create an employee
  • Update an employee
  • Get All employees
  • Delete an employee

Assume that employee is stored in Database, So we have to use a http connection to database. So Angular provides http connection wrapper in HttpClient class. The service has a dependency on class and services does the following things

  • Inject HttpClient method to service via constructor as seen in below example

Here is an Angular Service example

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from "rxjs/operators";
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {
  private employee_api_url: string = 'api.localhost.com';

  constructor(private httpClient: HttpClient) { }

  createEmployee(employee: any): Observable<any> {
    return this.httpClient.post(this.employee_api_url + '/create', employee)
      .pipe(map((resp: any) => resp.json()),
        catchError(error => this.throwError(error))
      )

  }
  getEmployees(): Observable<any> {
    return this.httpClient.get(this.employee_api_url + '/read')
      .pipe(map((resp: any) => resp.json()),
        catchError(error => this.throwError(error))
      )

  }
  updateEmployee(employee: any): Observable<any> {
    return this.httpClient.get(this.employee_api_url + '/update')
      .pipe(map((resp: any) => resp.json()),
        catchError(error => this.throwError(error))
      )

  }
  deleteEmployee(id: number): Observable<any> {
    return this.httpClient.delete(this.employee_api_url + '/delete/{id}')
      .pipe(map((resp: any) => resp.json()),
        catchError(error => this.throwError(error))
      )

  }
  throwError(error: any) {
    console.error(error);
    return Observable.throw(error.json().error || 'Server error');
  }

}

We have to write a test cases independently for each method of an class.

When you are writing an testcases for service , Normally you need to do following steps

  • Get an object of a class
  • Call a function method with passing an different values
  • Finally assert the result with expected values.

By default, angular cli creates an unit test class files also called spec files as below

Default generated code for angular unit testing service example

import { TestBed } from '@angular/core/testing';

import { EmployeeService } from './employee.service';

describe('EmployeeService', () => {
  let service: EmployeeService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(EmployeeService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

Let’s discuss about steps for spec file

describe('EmployeeService', () => {});

describe is an keyword from jasmine framework, It is to specify logical separation of for telling grouping of test cases. It contains beforeeach and it methods

beforeEach is used to initlize and declareall the dependencies and instances required for testcases defined in it methods.

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

TestBed is an angular testing module to load and run the angular component in test container.

    TestBed.configureTestingModule({});

As Angular service has a dependency on HttpClient, So to create an object of Service, to send http request and response, so you need to provide the instance of HttpClient object.

How do you create an HttpClient instance for angular service?

In General, In Unit testing, We don’t create an real instance of an class, We will create a mock instance of HttpClient Mocks is an fake object instead of real object. We don’t send real http request and response, Instead send fake request and response.

To create an mock instance of httpClient, Angular provides HttpClientTestingModule First, Import HttpClientTestingModule into your test spec file as seen below Add EmployeeService as a dependency to provider of TestBed

  TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [EmployeeService],
    });

Let’s discuss about test cases

  • Test cases for Check service instance is created or not
it('should be created', () => {
    const service: EmployeeService = TestBed.get(EmployeeService);
    expect(service).toBeTruthy();

  });
  • Test cases for angular service method observables

Sequence of steps to write a employee creation

  • First get the EmployeeService object from TestBed

  • Create a mock object on createEmployee method using spyOn method from jasmine

    spyon contains two methods

    • withArgs for input values to mock method
    • returnValue contains returns values which is Observable
  • Next, call real service method and subscribe to it, Inside subscribe check for assert of return value with actual and expected value..

  • Finally check whether service method is called or not using toHaveBeenCalled method

 it('Create an Employee on Service method', () => {
    const service: EmployeeService = TestBed.get(EmployeeService);
    let employeeServiceMock = spyOn(service, 'createEmployee').withArgs({})
      .and.returnValue(of('mock result data'))

    service.createEmployee({}).subscribe((data) => {
      console.log("called")
      expect(data).toEqual(of('mock result data'));
    }); 
    expect(service.createEmployee).toHaveBeenCalled();
  
  });

NullInjectorError: No provider for HttpClient in Unit Testing a service error

It looks like an HttpClientModule not properly imported in test case module

import { HttpClientTestingModule } from '@angular/common/http/testing';

In @ngModule of a component spec file,

you can include HttpClientModule as seen below

 //first testbed definition, move this into the beforeEach
TestBed.configureTestingModule({
  imports: [HttpClientTestingModule],
  providers: [EmployeeService]
});

Conclusion

You learned unit testing angular services with httpclient and dependencies and mocking observable methods.

THE BEST NEWSLETTER ANYWHERE
Join 6,000 subscribers and get a daily digest of full stack tutorials delivered to your inbox directly.No spam ever. Unsubscribe any time.

Similar Posts
Subscribe
You'll get a notification every time a post gets published here.