Introduction

This tutorial will help you understand the basics of unit testing for Angular HttpClient. Angular includes a testing module named HttpClientTestingModule that provides an HTTP mocking service, HttpTestingController to intercept the HTTP request and provide a mock response for it.

Setup

We begin by writing a service to fetch blogs from an application server.

Use the following Angular cli commands to generate a service.

Generate a module

ng g module blogs

Generate a service

ng g service blogs/blog

Now create the following classes inside the app folder.

Blog model class

blog.ts

export class Blog {
    title: string = "";
    content: string = "";
    createdAt: Date = new Date();
    author: string = "";
}

Blog Service

blog.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Blog } from './blog';

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

  static readonly API_ENDPOINT = "http://localhost:8090/api/blogs";

  constructor(private http: HttpClient) { }

  public fetchBlogs(): Observable<Blog[]> {
    return this.http.get<Blog[]>(BlogService.API_ENDPOINT);
  }

}

Now we have the basic building blocks of the application. Next, we can write unit tests to verify HTTP client communication between the Angular application and API server. 

Write Unit Test Case

Create a unit test case file as follows and import the HTTP testing module and its controller.

blog.service.spec.ts

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

HttpClient Testing Module

The Angular HTTP testing module provides a class named HttpTestingController which intercepts any HTTP request initiated by an Angular application. It also provides the stub response or error message to the HTTP request.

In nutshell, it creates a mock server with the desired response for a particular HTTP request. Next, create an instance of the HTTP testing controller to emulate mock http calls.

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports:[HttpClientTestingModule]
    });
    service = TestBed.inject(BlogService);
    httpCtrl = TestBed.inject(HttpTestingController);
  });

Unit Test For Success Response

it('Should return blogs from Http Get call.', () => {
    service.fetchBlogs()
      .subscribe({
        next: (response) => {
          expect(response).toBeTruthy();
          expect(response.length).toBeGreaterThan(1);
        }
      });
});

The above snippet executes the blog service, then accepts the response and verifies it. As stated earlier, HTTP calls are intercepted by HttpTestingController and return a successful response with a 200 status code.

Create success response

First, we need to create a mock HTTP request handler which will intercept the request originated for http://localhost:8090/api/blogs URL.

const mockHttp = httpCtrl.expectOne('http://localhost:8090/api/blogs');
const httpRequest = mockHttp.request;

This will acquire the handler for the HTTP request. We can use it to validate the request’s method type and send a mock response.

  expect(httpRequest.method).toEqual("GET");
  mockHttp.flush(BLOG_RESPONSE);

Error Response

The unit test case for HTTP error is shown in the snippet below.

it('Should return error message for Blog Http request.', ()=>{
    service.fetchBlogs()
    .subscribe({
        error: (error) => {
          expect(error).toBeTruthy();
          expect(error.status).withContext('status').toEqual(401);
        }
    });

    const mockHttp = httpCtrl.expectOne(BlogService.API_ENDPOINT);
    const httpRequest = mockHttp.request;

    mockHttp.flush("error request", { status: 401, statusText: 'Unathorized access' });
  });

Here we flush the error message with an HTTP 401 status code. And while subscribing, we bind the request to the error block and verify it.

Complete Unit Test

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'

import { BlogService } from './blog.service';

describe('BlogService', () => {
  let service: BlogService;
  let httpCtrl: HttpTestingController;

  const BLOG_RESPONSE = [
    {
      title: "How to create service in Angular",
      content: "Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups.",
      createdAt: Date.now(),
      author: "Zainul"
    },
    {
      title: "How to create module in Angular",
      content: "Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups.",
      createdAt: Date.now(),
      author: "Zainul"
    }
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule]
    });
    service = TestBed.inject(BlogService);
    httpCtrl = TestBed.inject(HttpTestingController);
  });

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

  it('Should return blogs from Http Get call.', () => {
    service.fetchBlogs()
      .subscribe({
        next: (response) => {
          expect(response).toBeTruthy();
          expect(response.length).toBeGreaterThan(1);
        }
      });

    const mockHttp = httpCtrl.expectOne(BlogService.API_ENDPOINT);
    const httpRequest = mockHttp.request;

    expect(httpRequest.method).toEqual("GET");

    mockHttp.flush(BLOG_RESPONSE);
  });

  it('Should return error message for Blog Http request.', () => {
    service.fetchBlogs()
    .subscribe({
        error: (error) => {
          expect(error).toBeTruthy();
          expect(error.status).withContext('status').toEqual(401);
        }
    });

    const mockHttp = httpCtrl.expectOne(BlogService.API_ENDPOINT);
    const httpRequest = mockHttp.request;

    mockHttp.flush("error request", { status: 401, statusText: 'Unathorized access' });
  });
});


Conclusion

Developers are under the impression that they need a production-ready API server to test their Angular application, but it is a half-truth. Rather, we just need to verify the HTTP client communication, which could be served by the mock server and the Angular HTTP testing module does exactly the same.

Source Code

You can find source code used in this tutorial on Github page.