How to write unit testing for Angular Service classes and httpclient dependencies Angular
- Admin
- Dec 31, 2023
- Angular-test
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 jasminespyon 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.