W3cubDocs

/Angular

HTTP client - Test requests

As for any external dependency, you must mock the HTTP backend so your tests can simulate interaction with a remote server. The @angular/common/http/testing library makes it straightforward to set up such mocking.

HTTP testing library

Angular's HTTP testing library is designed for a pattern of testing in which the app executes code and makes requests first. The test then expects that certain requests have or have not been made, performs assertions against those requests, and finally provides responses by "flushing" each expected request.

At the end, tests can verify that the app made no unexpected requests.

You can run these sample tests in a live coding environment.

The tests described in this guide are in src/testing/http-client.spec.ts. There are also tests of an application data service that call HttpClient in src/app/heroes/heroes.service.spec.ts.

Setup for testing

To begin testing calls to HttpClient, import the HttpClientTestingModule and the mocking controller, HttpTestingController, along with the other symbols your tests require.

// Http testing module and mocking controller
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

// Other imports
import { TestBed } from '@angular/core/testing';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

Then add the HttpClientTestingModule to the TestBed and continue with the setup of the service-under-test.

describe('HttpClient testing', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule ]
    });

    // Inject the http service and test controller for each test
    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
  });
  /// Tests begin ///
});

Now requests made in the course of your tests hit the testing backend instead of the normal backend.

This setup also calls TestBed.inject() to inject the HttpClient service and the mocking controller so they can be referenced during the tests.

Expect and answer requests

Now you can write a test that expects a GET Request to occur and provides a mock response.

it('can test HttpClient.get', () => {
  const testData: Data = {name: 'Test Data'};

  // Make an HTTP GET request
  httpClient.get<Data>(testUrl)
    .subscribe(data =>
      // When observable resolves, result should match test data
      expect(data).toEqual(testData)
    );

  // The following `expectOne()` will match the request's URL.
  // If no requests or multiple requests matched that URL
  // `expectOne()` would throw.
  const req = httpTestingController.expectOne('/data');

  // Assert that the request is a GET.
  expect(req.request.method).toEqual('GET');

  // Respond with mock data, causing Observable to resolve.
  // Subscribe callback asserts that correct data was returned.
  req.flush(testData);

  // Finally, assert that there are no outstanding requests.
  httpTestingController.verify();
});

The last step, verifying that no requests remain outstanding, is common enough for you to move it into an afterEach() step:

afterEach(() => {
  // After every test, assert that there are no more pending requests.
  httpTestingController.verify();
});

Custom request expectations

If matching by URL isn't sufficient, it's possible to implement your own matching function. For example, you could look for an outgoing request that has an authorization header:

// Expect one request with an authorization header
const req = httpTestingController.expectOne(
  request => request.headers.has('Authorization')
);

As with the previous expectOne(), the test fails if 0 or 2+ requests satisfy this predicate.

Handle more than one request

If you need to respond to duplicate requests in your test, use the match() API instead of expectOne(). It takes the same arguments but returns an array of matching requests. Once returned, these requests are removed from future matching and you are responsible for flushing and verifying them.

// get all pending requests that match the given URL
const requests = httpTestingController.match(testUrl);
expect(requests.length).toEqual(3);

// Respond to each request with different results
requests[0].flush([]);
requests[1].flush([testData[0]]);
requests[2].flush(testData);

Test for errors

You should test the app's defenses against HTTP requests that fail.

Call request.flush() with an error message, as seen in the following example.

it('can test for 404 error', () => {
  const emsg = 'deliberate 404 error';

  httpClient.get<Data[]>(testUrl).subscribe({
    next: () => fail('should have failed with the 404 error'),
    error: (error: HttpErrorResponse) => {
      expect(error.status).withContext('status').toEqual(404);
      expect(error.error).withContext('message').toEqual(emsg);
    },
  });

  const req = httpTestingController.expectOne(testUrl);

  // Respond with mock error
  req.flush(emsg, { status: 404, statusText: 'Not Found' });
});

Alternatively, call request.error() with a ProgressEvent.

it('can test for network error', done => {
  // Create mock ProgressEvent with type `error`, raised when something goes wrong
  // at network level. e.g. Connection timeout, DNS error, offline, etc.
  const mockError = new ProgressEvent('error');

  httpClient.get<Data[]>(testUrl).subscribe({
    next: () => fail('should have failed with the network error'),
    error: (error: HttpErrorResponse) => {
      expect(error.error).toBe(mockError);
      done();
    },
  });

  const req = httpTestingController.expectOne(testUrl);

  // Respond with mock error
  req.error(mockError);
});
Last reviewed on Mon Nov 14 2022

© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/http-test-requests