In the previous blog post we created some basic unit tests for the AngularJS movies controller. As I mentioned there was one set of tests missing and that was around the controller doing HTTP requests to the WebAPI backend. In a unit test typically don't want to do those test for real. First of all we don't want to take a dependency on some external state and equally important we don't want the overhead of those requests. Fortunately AngularJS makes it easy to fake those requests and test the controller bits in isolation.

 

The $httpBackend service

One of the files we included last time was angular-mocks.js which contains a number of things for our tests. The one art we are interested in now is $httpBackend. There are two implementations of $httpBackend in AngularJS. The normal version actually does your HTTP request using the browsers XMLHttpRequest object. However the second version of $httpBackend, part of angular-mocks.js, actually intercepts all HTTP requests and lets you send whatever fake response you want. All without ever going onto the real HTTP stack.

And because HTTP requests are done in an asynchronous way the fake $httpBackend service works the same way. Basically you specify what results you want for which requests and you tell it when to do the actual response. The three methods we are most interested in are when() and expect(), both used to arrange responses to requests, and flush() to actually push the requests.

 

Testing the HTTP requests

The unit tests are quite simple. I added two tests for both a successful and a failing request.

 

   1: describe("The moviesCtrl http requests", function () {
   2:     'use strict';
   3:  
   4:     var scope, httpBackend;
   5:  
   6:     beforeEach(module("myApp"));
   7:  
   8:     beforeEach(inject(function ($controller, $httpBackend) {
   9:         httpBackend = $httpBackend;
  10:  
  11:         scope = {};
  12:         $controller("moviesCtrl", {
  13:             $scope: scope
  14:         });
  15:     }));
  16:  
  17:     it("should populate the movies array when the HTTP request succeeds", function () {
  18:         var movies = [
  19:             { id: 1, title: "A movie" },
  20:             { id: 2, title: "Another movie" }];
  21:  
  22:         httpBackend.expect("GET", "/api/movies").respond(movies);
  23:  
  24:         httpBackend.flush();
  25:  
  26:         expect(scope.movies).toEqual(movies);
  27:     });
  28:  
  29:     it("should not populate the movies array when the HTTP request fails", function () {
  30:  
  31:         httpBackend.expect("GET", "/api/movies").respond(404);
  32:  
  33:         httpBackend.flush();
  34:  
  35:         expect(scope.movies).toBeUndefined();
  36:     });
  37: });


Quite simple and straightforward.

 

So what is wrong here?

These tests might be relatively simple but there is a bit of a problem. The movies controller should really not be concerned with HTTP request.  It should just get values from other services and stick to just orchestrating things between the different services.

So it turns out there are better ways of doing this. So we will come back to this code and refactor it to have a better separation of concern. One of the things that can help there is another service called $resource with is all about RESTful services.

 

Try it

The running application can be found here and the source on GitHub here.

 

Enjoy!


Blog Post by: Maurice