AngularJS Tests With An HTTP Mock

Tuesday, June 11, 2013

In the last post we tested a simple controller, so now let’s look at a controller that likes to communicate over the network.

(function (module) {

    var MoviesController = function ($scope, $http) {

        $http.get("/api/movies")
            .then(function (result) {
                $scope.movies = result.data;
            });
    };

    module.controller("MoviesController",
        ["$scope", "$http", MoviesController]);

}(angular.module("myApp")));

If you want a unit test to execute without network communication there are a couple options. You could refactor the controller to use a custom service to fetch movies instead of using $http directly. Then in a unit test it would be easy to provide a fake service that returns pre-canned responses.

Another option is to use angular-mocks. Angular mocks includes a programmable fake $httpBackend that replaces the real $httpBackend service. It is important to note that $httpBackend is not the same as the $http service. The $http service uses $httpBackend to send HTTP messages. In a unit test, we’ll use the real $http service, but a fake $httpBackend programmed to respond in a specific way.

Here is one approach:

describe("myApp", function () {

    beforeEach(module('myApp'));

    describe("MoviesController", function () {

        var scope, httpBackend;
        beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
            scope = $rootScope.$new();
            httpBackend = $httpBackend;
            httpBackend.when("GET", "/api/movies").respond([{}, {}, {}]);
            $controller('MoviesController', {
                $scope: scope,
                $http: $http
            });
        }));

        it("should have 3 movies", function () {
            httpBackend.flush();
            expect(scope.movies.length).toBe(3);
        });
    });
});

In the above test, $httpBackend is programmed (with the when method) to respond to a GET request with three objects (empty objects, since the controller in this example never uses the data, but only assigns the response to the model).

For the data to arrive in the model, the test needs to call the flush method. Flush will respond to all requests with the programmed responses. The flush method will also verify there are no outstanding expectations. Writing tests with expectations is a little bit different.

describe("myApp", function () {

    beforeEach(module('myApp'));

    describe("MoviesController", function () {

        var scope, httpBackend, http, controller;
        beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
            scope = $rootScope.$new();
            httpBackend = $httpBackend;
            http = $http;
            controller = $controller;
            httpBackend.when("GET", "/api/movies").respond([{}, {}, {}]);
        }));
      
        it('should GET movies', function () {
            httpBackend.expectGET('/api/movies');
            controller('MoviesController', {
                $scope: scope,
                $http: http
            });
            httpBackend.flush();
        });
    });
});

The expectation in the above code is registered with the expectGET method. When the test calls flush, flush will fail the test if the controller did not make all of the expect calls. Of the two tests in this post, I prefer the first style. Testing with expectations is the same as interaction testing with mocks, which I’ve learned to shy away from because interaction testing tends to be brittle.


Comments
gravatar Matthew Adams Tuesday, June 11, 2013
Good stuff! Quick q though - wouldn't it be more "angular-ish" to do the http work in a service, rather than in the controller itself? I realise that it is more a style issue than anything else, but I generally try to keep the work in the controller to a minimum: a bit of data shaping and view-state-ish stuff?
gravatar Scott Wednesday, June 12, 2013
@Matthew: yes, I'd say keeping $http stuff inside a service is a good idea.
gravatar Stuart Wednesday, June 12, 2013
Thanks Scott, you are providing newbie Angular devs with a valuable resource. I enjoy reading all of your Ang content!
gravatar Steve Gentile Thursday, June 13, 2013
I love how easy it is to write tests with Angular ! Thanks for the example!
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!