Mocking Http dependency in Angular

Mocking Http dependency in Angular

How to test Angular services that make Http calls

It is very common to have services that make use of the HttpClient service to make back-end calls. For example, you might have a service that makes a request to a back-end API and return results that you would like to display on the browser.

As explained in my previous post on this series of Angular Testing, dependencies that are injected to our services and component must be mocked. This isolates your test to only the component or service under test and it also gives you the capability to control what the injected dependency should return.

Here is an example of a service that makes a call to the github API:

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

export interface RepositoryInfo {
  login: string;
  id: string;
}

@Injectable({
  providedIn: 'root'
})
export class RepositoryService {
  constructor(private http: HttpClient) {}

  getUserIfo(name: string): Observable<RepositoryInfo> {
    return this.http.get<RepositoryInfo>(`https://api.github.com/users/${name}`)
  }
}

If we think about it, the http call (GET request in our example) can do a lot of things. We can pass it an options object that can :

  • Instruct the type of response to provide (just the body, or the full response, etc)
  • Provide http headers
  • Provide http parameters
  • Request response of a certain type
  • Request report progress

This range of capability brings us to an interesting question. How do we mock our http service to accommodate all these options?

Lucky for us, the Angular team has created a class for us that does exactly this - provide us with an easy way to test http calls.

Using the HttpTestingController

The HttpTestingController class provides us with the ability to mock our Http calls and control what response we get from those calls. In fact, as we go through examples in this post, we will see that we do a lot more than just control the response.

To use this class, we have to import the HttpClientTestingModule into our TestBed's module definition.

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

describe('RepositoryService', function () {
  let repositoryService: RepositoryService;
  let httpController: HttpTestingController;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        RepositoryService,
      ]
    });
    repositoryService = TestBed.inject(RepositoryService);
    httpController = TestBed.inject(HttpTestingController);
  });
});

Notice that we did not import the HttpClientModule - instead, we imported the HttpClientTestingModule.

Now that we have a reference to our Repository service, let's get going with testing our getUserInfo method. Can you think of what we could test (as in, what expect statements we should have)?

Here is my list of test to do for our getUserInfo method:

  • Make sure only a single http call is made
  • The http request URL should start with api.github.com/users
  • It should be a GET request
  • Check our getUserInfo matches our logic within the method - in this case, it's a simple return - hence, getUserInfo's response should match the response of our HTTP call.

Because the getUserInfo method returns us an Observable, we must subscribe to it. Within our subscription block, we need to write our expectations. Let's do that first.

    repositoryService.getUserIfo('testUser').subscribe(response => {
      expect(response.id).toEqual('ID');
      expect(response.login).toEqual('LOGIN')
    });

Remember that we are going to mock the response that we get from our HTTP call - more on that soon.

The next thing we need to do is tell our HttpController what kind of an HTTP request our method will make.

    let http = httpController.expectOne(req => req.url.startsWith('https://api.github.com/users'));

This line tells our controller that we will be making a single Http call and the call's URL will match the predicate function - in this case, the call will start with api.github.com/users.

Next we want to make sure that the call will be using the GET method. We use our response from the previous line to test our assertion:

expect(http.request.method).toEqual('GET');

The last thing we want to do is to initiate the actual HTTP mock call and pass it our mock data. Note that even though our subscription block has been defined (we subscribed to the getUserInfo( ... ) method), it isn't called until the following call is made:

    http.flush({
      id: 'ID',
      login: 'LOGIN'
    });

This should now initiate the mock Http call and set the response to be the value of what is passed to the flush method. Think of flush as clearing the buffer (at least that's how i remember it from my university days).

Let's put all this together:

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

describe('RepositoryService', function () {
  let repositoryService: RepositoryService;
  let httpController: HttpTestingController;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        RepositoryService,
      ]
    });
    repositoryService = TestBed.inject(RepositoryService);
    httpController = TestBed.inject(HttpTestingController);
  });

  it('should get user information', () => {
    repositoryService.getUserIfo('testUser').subscribe(response => {
      expect(response.id).toEqual('ID');
      expect(response.login).toEqual('LOGIN')
    });

    let http = httpController
      .expectOne(req => req.url.startsWith('https://api.github.com/users'));
    expect(http.request.method).toEqual('GET');

    http.flush({
      id: 'ID',
      login: 'LOGIN'
    });
  });
});

Notice that we have 4 expectations in testing our single method (getUserInfo). The last line (http.flush) is the line that makes an actual mock http request.

I hope this gives you some insight into testing HTTP calls made by your components or services. Next time you have to test something that is provided by a library, check if there a testing module you can use to assist you. For example, to test translation service provided by ngx-translate, you can use the TranslateTestingModule from the ngx-translate-testing package.

I have seen code that creates interceptors to mock response from HTTP calls. Please don't do that.

In my next post, I we go over testing asynchronous calls such as promises and setTimeout.