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 the following things

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

Unit testing is testing a piece of code with runs as expected.

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

Angular Service Unit Testing

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

  • asynchronous 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 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 the 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 a 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 the 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 that has Service methods for

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

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

  • Inject the HttpClient method to service via constructor as seen in the 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 case independently for each method of a class.

When you are writing a test case for service, you need to do the following steps.

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

By default, angular CLI creates 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 steps for the spec file

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

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

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

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

The testBed is an angular testing module to load and run the angular component in the 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, 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 a real instance of a class, We will create a mock instance of HttpClient. Mock is a fake object instead of real objects. We don’t send real HTTP requests and responses, Instead, send fake requests and responses.

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

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

Let’s discuss test cases.

  • Test cases to 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

The sequence of steps to write an employee creation

  • First, get the EmployeeService object from TestBed.

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

    spyon contains two methods

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

  • Finally, check whether the service method is called or not using the 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 does not import in the 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.