Testing HTTP services in Angular

Table of contents
Reading Time: 2 minutes

Prerequisites :

    1. 1. Understanding of Angular.
    1. 2. Understanding of Component’s unit tests in Angular
    1. 3. Understanding of Karma and Jasmine

Http Service

Let’s consider a simple service to get data using get method of Http service.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


import {Headers, Http} from '@angular/http';
import {Injectable, Inject} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
@Injectable()
export class AppService {
private userDataURL: string;
constructor(private http: Http) {
this.userDataURL = 'https://jsonplaceholder.typicode.com/users'
}
getData() {
let headers = new Headers({
'Content-Type': 'application/json'
});
return this.http
.get(this.userDataURL, {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: any) {
let body = res.json();
return body || {};
}
private handleError(error: any) {
let errMsg: string;
try {
if(JSON.parse(error._body)) {
errMsg = JSON.parse(error._body);
} else {
errMsg = 'Something went wrong. Please try again later.';
}
} catch(e){
errMsg = 'Something went wrong. Please try again later.';
}
return Observable.throw(new Error(errMsg));
}
}
view raw

app.service.ts

hosted with ❤ by GitHub

Let’s start with writing a test case for this service.

Configuring Testing Module for Service:

First of all, we need an instance of our service i.e. AppService. We can create one using the get method of TestBed.

let service = TestBed.get(AppService);

But for doing that, we also need to add the service to the providers of this testing environment. We will do that by configuring the testing module, like we do while testing our components.

Let’s do that in the beforeEach block



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


beforeEach(() => {
TestBed.configureTestingModule({
providers: [AppService]
})
});
view raw

test1.ts

hosted with ❤ by GitHub

Notice that we don’t have the compileComponents method. We need that only for components as it converts your html and css urls to inline code. Also, since we don’t need to call it, we don’t need to wrap it in async block.

The first test case I always add is to check if the module is configured properly and the instance of our class is defined or not.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


it('get initialized', () => expect(service).toBeDefined() );
view raw

test2.ts

hosted with ❤ by GitHub

You’ll get an error :
Error: No provider for Http!

Ofcourse, when we look at the AppService, we have an Http object as a dependency. We’ll have to provide that as well. And if you take a look at the Http class, it needs two parameters ConnectionBackend and RequestOptions as dependencies :

constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions)

So, we need them as well.

Angular makes our job easy by providing mock object for ConnectionBackend. Just import that with the BaseRequestOptions as the second parameter to Http Object :

import {MockBackend} from '@angular/http/testing';

import {Http, BaseRequestOptions} from '@angular/http';

Now we will use useFactory, to provide an instance of Http. If you’re not familiar with that, you can checkout Angular’s documentation on dependency injection here.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AppService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions],
}
]
});
service = TestBed.get(AppService)
});
view raw

test3.ts

hosted with ❤ by GitHub

So, in the above code, we have configured that whenever an instance of Http will be required, it will look into these providers to figure out how to create that instance. And for that we have provided a method that returns an Http object using the two parameters we imported earlier.

Now, your test case should work, as we should get an instance of AppService.

Testing Http Service:

Now, for testing the getData method, we obviously need to mock the connection that Http creates and makes an http request. But as we have already provided mock object of ConnectionBackend while providing Http instance, the test case won’t create an actual connection.
Also we can configure the response for each http request. We will do that using the connection property of MockBackend.

Let’s create an instance of mockBackend first using TestBed get method :
mockBackend = TestBed.get(MockBackend);

This is our final test case :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


//import {MockBackend, MockConnection} from '@angular/http/testing';
//import {BaseRequestOptions, Http, Headers, ResponseOptions, Response} from '@angular/http';
it('be able to get data', () => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: [{name : 'Jerry'},{name : 'George'},{name : 'Elaine'},{name : 'Kramer'}],
status: 200,
headers: new Headers({
'Content-Type': 'application/json'
})
})));
});
service.getData().subscribe(data => {
expect(data).toEqual([{name : 'Jerry'},{name : 'George'},{name : 'Elaine'},{name : 'Kramer'}]);
});
});
view raw

test4.ts

hosted with ❤ by GitHub

In the above test case, for every http request getData method makes, it will get the response that we have provided.

We can also cover the error scenario using the mockError method like this :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


it('not be able to get data', async(() => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error());
});
service.getData().subscribe(data => data,
error => {
expect(error.toString()).toContain('Something went wrong. Please try again later.');
});
}));
view raw

test5.ts

hosted with ❤ by GitHub

You can find the code for this blog here


Written by 

Principal Architect at Knoldus Inc

1 thought on “Testing HTTP services in Angular3 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading