Egor Panok

Full Stack JavaScript Developer

Unit Testing of HTTP requests in Angular

January 28, 2019 - 4 min read

Loading Angular Components Dynamically

TL;DR: To test Angular services performing HTTP requests we test our services actually doing the expected requests to the real API, but instead of expecting the real response, we intercept the request and ‘flush’ it with the mock data.

Use Case

Let’s suppose that we have to test a UserService, containing two methods: getAll() and getOne(id), requesting the https://jsonplaceholder.typicode.com/ user’s endpoints.

UserService

That’s how it could look like:

// user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { IUser } from './models/user.model';

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

  basicUrl = 'https://jsonplaceholder.typicode.com';

  constructor(
    private _httpClient: HttpClient
  ) { }

  getAll(): Observable<IUser[]> {
    return this._httpClient.get<IUser[]>(`${this.basicUrl}/users`);
  }

  getOne(id: number): Observable<IUser> {
    return this._httpClient.get<IUser>(`${this.basicUrl}/users/${id}`);
  }
}

For the sake of completeness, let’s define the models:

// address.model.ts

export interface IAddress {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
}

// company.model.ts

export interface ICompany {
    name: string;
    catchPhrase: string;
    bs: string;
}

// user.model.ts

export interface ICompany {
    name: string;
    catchPhrase: string;
    bs: string;
}

Testing UserService

To start testing calls to HttpClient, we have to import the TestBed, HttpClientTestingModule and the mocking controller HttpTestingController along with our domain specific dependencies: IUser and UserService.

// user.service.spec.ts

import { TestBed } from "@angular/core/testing"
import {
    HttpClientTestingModule,
    HttpTestingController
} from "@angular/common/http/testing"
import { IUser } from "./models/user.model"
import { UserService } from "./user.service"

Since we have two methods to test, it’s reasonable to define common initialization procedure in the beforeEach() section. In our case it will be the API’s URL, mock users and TestBed’s configuration (which emulates Angular NgModule() configuration in the testing environment):

    basicUrl = 'https://jsonplaceholder.typicode.com';
    mockUsers = [{
        id: 1,
        name: 'Leanne Graham',
        username: 'Bret',
        email: 'Sincere@april.biz',
        address: {
          street: 'Kulas Light',
          suite: 'Apt. 556',
          city: 'Gwenborough',
          zipcode: '92998-3874',
        },
        phone: '1-770-736-8031 x56442',
        website: 'hildegard.org',
        company: {
          name: 'Romaguera-Crona',
          catchPhrase: 'Multi-layered client-server neural-net',
          bs: 'harness real-time e-markets'
        }
        }, {
        id: 2,
        name: 'Ervin Howell',
        username: 'Antonette',
        email: 'Shanna@melissa.tv',
        address: {
          street: 'Victor Plains',
          suite: 'Suite 879',
          city: 'Wisokyburgh',
          zipcode: '90566-7771'
        },
        phone: '010-692-6593 x09125',
        website: 'anastasia.net',
        company: {
          name: 'Deckow-Crist',
          catchPhrase: 'Proactive didactic contingency',
          bs: 'synergize scalable supply-chains'
        }
    }];

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

  });

});

So now we’re ready to test it. If you use Angular CLI to generate services, then it also generates the first test which simply verifies that the service is created:

// user.service.spec.ts

// ...
describe("UserService", () => {
    it("should be created", () => {
        const service: UserService = TestBed.get(UserService)
        expect(service).toBeTruthy()
    })
})

This sample show us how we can retrieve the service leveraging the TestBed.

Now we’re ready to write our first own case:

// user.service.spec.ts

// ...
describe("UserService", () => {
    // ...

    it("should get the list of users", () => {
        const service: UserService = TestBed.get(UserService)
        const backend: HttpTestingController = TestBed.get(
            HttpTestingController
        )

        service.getAll().subscribe(users => {
            expect(users).toEqual(mockUsers)
        })

        backend
            .expectOne({
                method: "GET",
                url: `${basicUrl}/users`
            })
            .flush(mockUsers)
    })
})

Here we’re retrieving the UserService along with the instance of the HttpTestingController which will allow us intercept the requests and flush them returning mock data. Note that in this test case we’re using UserService.getAll() method which makes the http request itself. This request is intercepted and flushed by the instance of the HttpTestingController which outputs the mockUsers. Then we’re verifying this response in the getAll()‘s observable’s subscription.

Here’s the test for the UserService.getOne(id):

// user.service.spec.ts

// ...
describe("UserService", () => {
    // ...

    it("should get user by id", () => {
        const service: UserService = TestBed.get(UserService)
        const backend: HttpTestingController = TestBed.get(
            HttpTestingController
        )
        const id = 1

        service.getOne(id).subscribe(user => {
            expect(user).toEqual(mockUsers[id])
        })

        backend
            .expectOne({
                method: "GET",
                url: `${basicUrl}/users/${id}`
            })
            .flush(mockUsers[id])
    })
})

Alright, let’s run the tests with ng test command:

Karma Tests Passed

All our tests passed

Summary

That kind of tests might seem a bit silly, but they do their job - they verify that our service methods employing the http requests, actually do that requests. In the real world, the use cases can be more complicated, for instance, performing several http requests, thus making the need of such tests much clearer.

Feel free to play with the code using this repo.

Happy coding!


Written by Egor Panok, full stack JavaScript Developer who loves building useful things. Follow him on Twitter

© 2020, Egor Panok, Full Stack JavaScript Developer