What is Angular testing?
Angular testing is a core feature available in every project set up with Angular CLI. An Angular unit test aims to uncover issues such as incorrect logic, misbehaving functions, etc. You know that testing each possible behavior would be very tedious, insufficient, and ineffective. One of the easiest ways to solve this problem is by writing test cases for each block. Do you need to wait until your users complain that what data they get when clicking on the button? By writing test cases for your blocks(components, services, etc.) you can easily detect where is a break!
Get started with Angular testing:
When we create a new project, it also downloads and installs everything we need to test an application. Angular uses the Jasmine test framework for testing an application and Karma test runner for run the test.
Test file has an extension of .spec.ts. It is in the same folder for a file for which we are writing tests.
To run the test, write the following command.
ng test
When we run this command, the test result is shown in the command prompt. The above command also opens the chrome browser and shows the test results.
http://localhost:9876
1. Jasmine:
Jasmine is the test framework for test the javascript code. Jasmine is a dependent free which means it can be run without DOM.
Following is the basic example of test code.
describe("Name of the test", function() { var msg; it("Test description", function() { msg = 'Hello'; expect(msg).toEqual('Hello'); }); });
Inbuilt method of Jasmine to use in creating test cases.
describe(): describe(string, function) functions take a title and a function containing one or more specs and are also known as a suite or test suite. it(string, function) functions take a title and a function containing one or more expectations and are also known as specs.
beforeEach(): As the name implies, the beforeEach function is called once before each spec in the describe().
afterEach(): As the name implies, the afterEach function is called once after each spec in the describe().
it(): Defines a single spec. It contains one or more than one expects.
expects(): it describes an expected piece of behavior in the application. i.e., what we want in response.
fail(): It marks a test failed.
2. Karma:
Karma is a test runner for javascript applications. When we run the ng test command, it builds the app in watch mode and it launches the Karma test runner.
3. HttpTestingController:
import { HttpTestingController } from '@angular/common/http/testing';
4. HttpClientTestingModule:
The HttpClientTestingModule injects HttpTestingController to expect and flush requests in our tests. It is imported as follows.
import { HttpClientTestingModule } from '@angular/common/http/testing';
5. TestBed
import { TestBed } from '@angular/core/testing';
configureTestingModule(): Allows overriding default providers, directives, pipes, modules of the test injector, which are defined in test_injector.js
Testing GET Method:
In this example, we will test the HTTP GET method using HttpClient.get method.
employee.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { map, catchError, tap } from 'rxjs/operators'; import { Employee } from './employee'; @Injectable({ providedIn: 'root' }) export class EmployeeService { empUrl = "/api/employees"; constructor(private http: HttpClient) { } getAllEmployees(): Observable<Employee[]> { return this.http.get<Employee[]>(this.empUrl).pipe( tap(employees => console.log("employees: " + JSON.stringify(employees))), catchError(this.handleError<Employee[]>([])) ); } private handleError<T>(result = {} as T) { return (error: HttpErrorResponse): Observable<T> => { console.error(error); return of(result); }; } }
employee.ts
export interface Employee { id: number; name: string; }
We will test getAllEmployees() method of employee service. We are testing the following scenario.
1. Should return expected employees by calling once.
2. should be OK returning no employee.
3. should return expected employees when called multiple times.
employee.service.spec.ts
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Employee } from './employee'; import { EmployeeService } from './employee.service'; //Testing of EmployeeService describe('EmployeeService', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; let empService: EmployeeService; beforeEach(() => { //Configures testing app module TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ EmployeeService ] }); //Instantaites HttpClient, HttpTestingController and EmployeeService httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); empService = TestBed.inject(EmployeeService); }); afterEach(() => { httpTestingController.verify(); //Verifies that no requests are outstanding. }); describe('#getAllEmployees', () => { let expectedEmps: Employee[]; beforeEach(() => { //Dummy data to be returned by request. expectedEmps = [ { id: 2001, name: 'John' }, { id: 2002, name: 'Wick' }, ] as Employee[]; }); //Test case 1 it('should return expected employees by calling once', () => { empService.getAllEmployees().subscribe( emps => expect(emps).toEqual(expectedEmps, 'should return expected employees'), fail ); const req = httpTestingController.expectOne(empService.empUrl); expect(req.request.method).toEqual('GET'); req.flush(expectedEmps); //Return expectedEmps }); //Test case 2 it('should be OK returning no employee', () => { empService.getAllEmployees().subscribe( emps => expect(emps.length).toEqual(0, 'should have empty employee array'), fail ); const req = httpTestingController.expectOne(empService.empUrl); req.flush([]); //Return empty data }); //Test case 3 it('should return expected employees when called multiple times', () => { empService.getAllEmployees().subscribe(); empService.getAllEmployees().subscribe( emps => expect(emps).toEqual(expectedEmps, 'should return expected employees'), fail ); const requests = httpTestingController.match(empService.empUrl); expect(requests.length).toEqual(2, 'calls to getAllEmployees()'); requests[0].flush([]); //Return Empty body for first call requests[1].flush(expectedEmps); //Return expectedEmps in second call }); }); });