Testing the Angular $http Service
As a number of smaller applications I've worked on have grown there has often been a requirement to integrate data and functionality from outside service using their API's. When working with Angular at the simplest level this often involves using the $http service, so far so good. However, I would often find that while I could get the requests themselves working and functioning for end to end testing actually mocking the API calls was a real blocker. In the end as ever this turned out to be relatively simple to solve and I hope that this short post can help anyone out there in the future who is also stumbling around this issue.
Mocking Mocking Mocking
With a blank boilerplate setup (as detailed in this post, ensuring that Angular Mocks are installed) I now want to be able to create the functionality within my application that will allow be to pull in data from an external API and test that this is being done correctly. Starting with the following blank controller:
var App = angular.module('App', []);
App.controller('Controller', [function(){
}]);
Now to begin with we need to setup our tests to use this controller; in our test file we start with:
describe('Controller', function(){
var ctrl;
beforeEach(module('App'));
beforeEach(inject(function($controller){
ctrl = $controller('Controller');
}));
});
We can now reference the controller properly in our test and check the services and methods that it is calling.
The key part to all of this comes in the inclusion of Angular Mocks that provide us with access to the $httpBackend service. This is a pre-built mock that will in effect take any of the calls made by our program during our tests and ensure that they do not leave the system. Why do we want to do this? In short to reduce the load placed on our own and the API vendors systems with unwanted and un-needed traffic. In the same way we just injected our controller into our test we now need to inject the $httpBackend service using another service: $injector.
beforeEach(inject(function($injector){
$httpBackend = $injector.get('$httpBackend');
}));
We now have everything that is required and we can go ahead with writing that test:
it('makes and API call', inject(function($http){
$httpBackend
.expect('GET', 'https://api.github.com/')
.respond(200, {foo:'bar'});
$httpBackend.flush();
expect(ctrl.products).toEqual({foo:'bar'});
}));
Going through the test we first inject the $http function that is required for $httpBackend. Then we tell our test how to handle a GET call to the API by asking it to respond with the object {foo:'bar'}. The final part to note is that we need to run the .flush() method on $httpBackend to allow the test to execute synchronously. Finally we can then set our expectation to receive our mocked return object. Quickly padding out the controller:
App.controller('Controller', ['$http', function($http){
var self = this;
self.apiCall = function(){
$http.get('https://api.github.com')
.then(function(response){
// do something here with response.data
});
};
self.apiCall();
}]);
Should now give us the ability to get the green light from Karma:
Executed 1 of 1 SUCCESS