AngularJS, code.explode, Coding

Mocking Promises in Unit Tests

If you’ve spent some time writing services in AngularJS that front-end Web APIs, you have probably used the $http service. One of the prevalent code patterns that tends to develop involves calling the .then() method on the promise that is returned from the various $http methods. This in effect waits to execute code until the asynchronous request returns and the promise is resolved.

The more complicated the Web API you are interacting with, the more your controller code tends to end up with several bits of code as shown below:

    user.requestCurrentUser().then(function() {
        $scope.currentUser = user.getCurrentUser();
    });

Testing this code can be a bit problemsome for newcomers to AngularJS. This article shows a simple way to mock your services to provide similar functionality so you can unit test your controllers that contain these type of code patterns.

A Simple Service that uses $http

To demonstrate how to test a controller that contains the above code pattern, we first need to create a simple service which uses the $http service and returns the promise from the $http() method.

Below is a simple service that provides an array which holds the returned data and a single method requestPeople which requests the data from the server, returns a promise and then updates the array with the returned data once the request completes:

    angular.module('people.service', []).
        factory('people', ['$http', function($http) {
            var people = {
                peopleStore = [],

                requestPeople: function() {
                    var url = '/data/people.json';

                    return $http.get(url).success(function(data) {
                        peopleStore = data;
                    }).error(function() {
                        peopleStore = [];
                    });
                }
            };

            return people;
        }]);

We will next create a simple app and controller that will use the service.

    angular.module('sampleApp', ['people.service']).
        config(['$routeProvider', function($routeProvider) {
            $routeProvider.
                when('/', {templateUrl:'partials/peoplelist.html', controller:PeopleListController);
        }]);


    function PeopleListController($scope, people) {
        $scope.peopleList = [];

        $scope.init = function() {
            people.requestPeople().then(function() {
                $scope.peopleList = people.peopleStore;
            });
        };
    }

The controller’s init method calls the requestPeople() method of the service and chains a call to the then() method of the returned promise. Once the promise is resolved, the controller populates the peopeList in it’s scope with the data stored in the service.

And below is the contents of the peoplelist.html file, which displays the people using a ng-repeat:

    <div class="container-fluid">
        <div class="row-fluid">
            <h2>People List</h2>
        </div>
        <div class="row-fluid">
            <div class="well">
                <div class="span1">Name</div>
                <div class="span2">Email</div>
                <div class="span3">Bio</div>
                <div class="span1"><a href="#/new"><span class="icon icon-plus"></span></a></div>
            </div>
            <div class="row-fluid" ng-repeat="person in PeopleList">
                <div class="span1" ng-bind="person.FirstName + ' ' + person.LastName"></div>
                <div class="span2" ng-bind="person.Email"></div>
                <div class="span3" ng-bind="person.Bio"></div>
                <div class="span1"><a href="#/edit/{{$index}}"><span class="icon icon-edit"></span></a></div>
            </div>
        </div>
    </div>

Mocking Out the Service

Now that we have our code, let’s write some unit tests for the controller.

    describe('People List Controller', function() {
        var scope;
        var peopleService;
        var controller;
        var q;
        var deferred;

        // define the mock people service
        beforeEach(function() {
            peopleService = {
                peopleStore = [{FirstName:"Jim", LastName:"Lavin", Email:"jlavin@jimlavin.net", Bio:"Creator and Host of Coding Smackdown TV"}],

                requestPeople: function() {
                    deferred = q.defer();
                    return deferred.promise;
                }
            };
        });

        // inject the required services and instantiate the controller
        beforeEach(inject(function($rootScope, $controller, $q) {
            scope = $rootScope.$new();
            q = $q;
            controller = $controller(PeopleListController, { $scope:scope, people: peopleService });
        }));

        it('should call requestPeople on the people service when init is called' function() {
            spyOn(peopleService, 'requestPeople').andCallThrough();

            scope.init();

            deferred.resolve();

            scope.$root.$digest();

            expect(peopleService.requestPeople).toHaveBeenCalled();
        });

        it('should populate the peopleList when init is called' function() {
            scope.init();

            deferred.resolve();

            scope.$root.$digest();

            expect(scope.peopleList).not.toBe([]);
        });
    });

Although the unit test may seem to be a bit complicated it is rather straight forward.

Initially, we create variables to hold the scope, mock service, a reference to the $q service and a reference of the deferred object created when we call q.defer() in our mock service.

We need to hold on to a reference to the $q service from AngularJS, so that we can call it when ever the requestPeople method of our mock service is called.

We’ll also need to hold on to the deferred object created by the call to q.defer() so we can resolve the promise, which in turn will execute the then() clause in our controller’s code.

Next, we define our mock service in a beforeEach() call so that each time a test is executed we have a clean version of our mock service.

Our mock service is pretty simple. First we make sure that at least each of the properites and methods the controller will access is defined, in this case that would be peopleStore and requestPeople. We then pre-populate the peopleStore with some default data. Finally, we mock out the requestPeople method by creating a deferred object, store the deferred object off and return the promise.

Next we make another call to beforeEach() that injects the services we need from AngularJS, creates the scope for the controller, stores off the $q service so our mock service can call it and then we finally instantiate the PeopleListController passing in the scope and our mock service.

Now we can go about creating our unit test scenarios.

In the first scenario we’ll check to see if the init method of the controller calls the requestPeople method of the people service. To do this we’ll use Jasmine’s spyOn() method which intercepts the call to the requestPeople method and tracks how many times the method is called. We also need to tell Jasmine what to do after it intercepts the call, we can either have it return a value, call a different function or have it call through to the original implementation. In our case we’ll just have it call through to the original implementation of our mock service.

Next we’ll call the init method on the controller, which should invoke our mock service that in turns creates the deferred object and stores it off and returns the promise to the controller which then waits for the promise to be resolved.

We then resolve the promise by calling deferred.resolve() and since we are outside the AngularJS world we have to call digest() on the scope’s root to have AngularJS invoke the then() clause in our controller. Once this happens the code in the then() clause of our controller will be executed and we can execute our assertions to make sure the code performed as we expected it to, in this case we check to see if the the requestPeople method of the service was called.

In the second scenario, we want to make sure that the then() clause was called and that the controller populated the peopleList on it’s scope with the data from the service. This is done in a similar manner, but since we do not care if the requestPeople method is called, we leave out the call to spyOn() and just call the controller’s init method, resolve the promise and call digest on the root scope so the then() clause is executed. Finally, we check to see that the peopleList in the controller’s scope is populated with the test data.

You could use the same scenario to handle reject processing as well by calling deferred.reject() instead of deferred.resolve().

Below is a jsFiddle that has a working example of the code above:

The Wrap Up

So in this article, I covered how to build a simple service that uses the $http service from AngularJS; how to use a promise returned from the $http service to wait until the asynchronous call completes by using the then() method of the promise, as well as how to write a mock service that returns promises so you can unit test your code.

Hopefully, this article will help you to easily write unit tests for your controllers which contain code that uses promises and get you over some of the hurdles of learning a new framework.

As always, drop me a comment on other AngularJS topics you’d like to see more tutorials on.

Standard
Loading Facebook Comments ...

9 thoughts on “Mocking Promises in Unit Tests

  1. Interesting technique, although I’m wondering, is it worth it? My personal pattern is to pass a callback function from the controller to the service. When the data is loaded, the service triggers the callback. No need for ‘q’ nor ‘deferred’ nor ‘scope.$root.digest()’ in the unit tests.

    You may question having your ‘people’ in both your service (peopleStore) *and* your controller (peopleList), introducing redundancy and possibly inconsistency. I’d keep a single reference in the service. But maybe this was for the sake of the example. Or maybe I’m doing it wrong 🙂

    IMO Angluar is lacking idiomatic best practices: shall I store my data in the service, or in the controller, using a callback function like I do or shall I go for the promise() like in this example? It’s difficult to get it right on your own. What appears to be a sound solution may turn out to be difficult to maintain or to test afterwards.

    Typo: $q=$q; should read q=$q;

    Thanks for the post, it’s nice to compare flavors of angular-fu.

    • Sorry that you find my example a little terse. In my other examples I have provided full examples, I just didn’t see the need for this article. But, I’ll be more than happy to provide at least a fiddle in the future.

      Thanks for your feedback.

  2. Pingback: Promises | Pearltrees

  3. Pingback: AngularJS, spies, and resolving promises

  4. Pingback: Build a Single Page App with AngularJS and the Salesforce REST API

Leave a Reply to dnr Cancel reply