How to Test an Angular HTTP Client

Jan 03, 2023
Angular

In this article we’ll write tests for an Angular service that makes HTTP requests using HttpClient. The tests use HttpTestingController to mock the behaviour of HttpClient.

The complete Angular app can be found here: https://github.com/minibuildsio/stampy-app.

Example HTTP Client Service

The service below interacts with a stamp collection tracker API called Stampy. The service gets a list of all stamps by making a GET request to /api/stamps and adds a new stamp by making a POST request to /api/stamps with a JSON body like {"name": "Two Penny Blue"}.

export class StampyClientService {

  constructor(private http: HttpClient) { }

  getStamps(): Observable<Stamp[]> {
    return this.http.get<Stamp[]>('/api/stamps');
  }

  addStamp(name: string): Observable<Stamp> {
    return this.http.post<Stamp>('/api/stamps', {name});
  }
}

The service is a fairly simple wrapper around HttpClient, making use of the get and post methods. So, to test this service we need a mock of HttpClient that we can set up canned responses for certain calls to methods and verify what arguments where provided. Angular provides a mock version of HttpClient called HttpTestingController in HttpClientTestingModule.

Configuring the Test to Use a Mocked HTTP Client

To use the HttpTestingController we need to configure the test bed to import HttpClientTestingModule this takes the place of HttpClientModule used by the real application. This results in StampyClientService being provided with a mocked version of HttpClient instead of the real one.

The HttpTestingController instance can be retrieved using TestBed.get(HttpTestingController). We’ll keep a reference to this called httpMock as we’ll need it to set up the mocked responses, etc.

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

import { StampyClientService } from './stampy-client.service';
import { Stamp } from '../model';

describe('StampyClientService', () => {
  let httpMock: HttpTestingController;
  let service: StampyClientService;

  beforeEach(() => {
    TestBed.configureTestingModule({ imports: [HttpClientTestingModule] })
    httpMock = TestBed.get(HttpTestingController);
    service = TestBed.get(StampyClientService);
  });

  ...

});

Verify the Expected HTTP Request Was Made

The test below verifies that the addStamp(...) method of the service makes the expected HTTP request and handles the response as expected.

The test starts by calling the addStamp(...) method of the service and subscribing to the Observable<Stamp> it returns. The observable will be pending until request.flush(...) is used to set the response after which the assertions in the subscribe callback will be run.

Next we set up the http mock to expect a request to be made to /api/stamps and use the flush(...) to set the response body of the request. HttpTestingController will keep the details of the request made to it in the request field allowing us to assert that the request was a POST request, and had the expected body. Finally, httpMock.verify() checks that there aren’t any more pending requests.

it('add stamp makes post request to /api/stamps', done => {
  service.addStamp('DC Collection - Batwomen').subscribe(stamp => {
    expect(stamp.id).toEqual(1);
    expect(stamp.name).toEqual('DC Collection - Batwomen');
    done();
  });

  let request = httpMock.expectOne('/api/stamps')
  request.flush(new Stamp(1, 'DC Collection - Batwomen'));

  expect(request.request.method).toEqual('POST');
  expect(request.request.body).toEqual({name: 'DC Collection - Batwomen'});

  httpMock.verify();
});

Note: This test is asynchronous hence we need to use the done callback to tell the framework when the test is complete, otherwise the expects in the subscribe will not be called.