Angularās new HttpClient has a testing module, HttpClientTestingModule, that makes it easy to unit test HTTP requests. In this short post weāll go over how to setup a simple unit test for an http GET request using that module, and in turn it should help demonstrate the capabilities of that new testing module.
Since HttpClient is available only starting with Angular 4.3, the following applies to Angular 4.3+. Have a look at this introduction if you're new to unit testing in Angular.
Service + Component
For this post weāll be working with a simple service that gets data from an endpoint and a component that calls that service to populate a list of users in the componentās OnInit hook.
Hereās our service:
data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest } from '@angular/common/http';
@Injectable()
export class DataService {
url = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) {}
getData() {
const req = new HttpRequest('GET', this.url, {
reportProgress: true
});
return this.http.request(req);
}
}
And our component looks like the following:
app.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { DataService } from './data.service';
@Component({ ... })
export class AppComponent implements OnInit {
users: any;
constructor(private dataService: DataService) {}
ngOnInit() {
this.populateUsers();
}
private populateUsers() {
this.dataService.getData().subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Sent:
console.log('Request sent!');
break;
case HttpEventType.ResponseHeader:
console.log('Response header received!');
break;
case HttpEventType.DownloadProgress:
const kbLoaded = Math.round(event.loaded / 1024);
console.log(`Download in progress! ${kbLoaded}Kb loaded`);
break;
case HttpEventType.Response:
console.log('šŗ Done!', event.body);
this.users = event.body;
}
});
}
}
Testing Setup
Now weāll setup a spec file for our data service and include the necessary utilities to test out the HttpClient requests. On top of HttpClientTestingModule, weāll also need HttpTestingController, which makes it easy to mock requests:
data.service.spec.ts
import { TestBed, inject } from '@angular/core/testing';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController
} from '@angular/common/http/testing';
import { DataService } from './data.service';
describe('DataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
});
it(
'should get users',
inject(
[HttpTestingController, DataService],
(
httpMock: HttpTestingController,
dataService: DataService
) => {
// ...our test logic here
}
)
);
});
We use the inject utility to inject the needed services into our test.
With this in place, we can add our test logic:
data.service.spec.ts (partial)
const mockUsers = [
{ name: 'Bob', website: 'www.yessss.com' },
{ name: 'Juliette', website: 'nope.com' }
];
dataService.getData().subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Response:
expect(event.body).toEqual(mockUsers);
}
});
const mockReq = httpMock.expectOne(dataService.url);
expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush(mockUsers);
httpMock.verify();
Thereās quite a bit going on, so letās break it down:
- First we define a couple of mock users that weāll test against.
- We then call the getData method in the service that weāre testing and subscribe to returned observable.
- If the HttpEventType is of type Response, we assert for the response event to have a body equal to our mock users.
- We then make use of the HttpTestingController (injected in the test as httpMock) to assert that one request was made to the serviceās url property. If no request is expected, the expectNone method can also be used.
- We can now make any number of assertions on the mock request. Here we assert that the request hasnāt been cancelled and the the response if of type json. Additionally, we could assert the requestās method (GET, POST, ā¦)
- Next we call flush on the mock request and pass-in our mock users. The flush method completes the request using the data passed to it.
- Finally, we call the verify method on our HttpTestingController instance to ensure that there are no outstanding requests to be made.
Hereās our complete test setup:
data.service.spec.ts
import { TestBed, inject } from '@angular/core/testing';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController
} from '@angular/common/http/testing';
import { DataService } from './data.service';
describe('DataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
});
it(
'should get users',
inject(
[HttpTestingController, DataService],
(httpMock: HttpTestingController, dataService: DataService) => {
const mockUsers = [
{ name: 'Bob', website: 'www.yessss.com' },
{ name: 'Juliette', website: 'nope.com' }
];
dataService.getData().subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Response:
expect(event.body).toEqual(mockUsers);
}
});
const mockReq = httpMock.expectOne(dataService.url);
expect(mockReq.cancelled).toBeFalsy();
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush(mockUsers);
httpMock.verify();
}
)
);
});
It may seem like a lot of boilerplate at first, but with this in place youāll be ready to test out all kinds of http request scenarios.