How to handle angularJS promises in jasmine unit tests

When developing unit tests in an angularJS app, you eventually stumble about a problem that can be quite frustrating to handle it in a clean und concisive way: promises.

Lets have a look at an example method in a controller:

$scope.method = function() {
    someService.loadData().then(function(data) {
       $scope.data = data;
    }
}

This method is calling loadData() on an angularJS service which is returning a promise, which could be resolved only after a $http call is finished.

How to handle that in a unit test?

Clearly, you should mock someService, thats easy. But what to do with the promise? In my first couple of tests, I tried to reimplement promises somewhat and ended with ugly constructs like this:

someServiceMock = {
    loadData: jasmine.createSpy().andCallFake(function() {
        then: jasmine.createSpy()
    }
}

This way I had to call the resolve or reject functions in my test manually, like so:

it('should be a lot easier', function() {
   controllerScope.someMethod();

   resolveFunction = someServiceMock.loadData().then.mostRecentCall.args[0];
   resolveFunction('resolveData');

   expect(controllerScope.data).toBe('resolveData');
});

That worked, but its ugly as hell.

So, after some studying with google I just refactored my tests with a much simpler solution, which uses the original $q service to resolve promises and calls the appropriate function arguments automatically.

Defining the mock service changes a bit and needs to be in an inject function as $q service is needed:

beforeEach(function() {
    inject(function(_someService_, _$q_, _$rootScope) {
        var deferred = _$q_.defer();
    
        someService = _someService;
        rootScope = _$rootScope_;
    
        deferred.resolve('resolveData');
        spyOn(someService, 'loadData').andReturn(deferred.promise);
    })
})

it('is now a lot easier', function() {
   controllerScope.someMethod();
   rootScope.$apply();
   expect(controllerScope.data).toBe('resolveData');
}

The loadData() spy is now always returning a resolved promise.

Of course, you can (and maybe should be) resolve the promise in the test itself if the data is different when you have more than one test.

The catch is though, that you need to call rootScope.$apply() method manually in your test as promises only get resolved during a angular $digest phase. After this method is finished, all promises are really resolved and you can test for the expected outcome.

13 thoughts on “How to handle angularJS promises in jasmine unit tests

  1. Hi Marco, thanks! That worked for me without the $rootScope.$apply() call.

    The promise must be resolved before any digest and that’s what the resolve method on the promise is supposed to do. Maybe you want to try it without that call. I see no reason not work. Nice article though!

  2. Mmmm… After I removed an $http from my controller and consequently the httpBackend.flush() my test started to fail as the promise resolved result as undefined. I tried your $rootScope.apply() call and it indeed started working again. It’s a bid odd this would be needed. I would expect the resolve to be enought but I guess it need that to process that assignment inside then success callback. Cheers!

  3. I have a problem on how to use jasmine spy objects to test controllers that depends on a service and I have been tussling with this issue for some time now as I am quite new to AngularJS based developments. While searching for a possible fix I stumbled on your article and attempted to implement test strategy suggested in your article but ran into further issues. Could you please suggest what changes should be made in order to implement this test correctly?

    In this controller I am trying to test the user login process and it calls loginRequest method in WebService to send out user credentials as a login message. A login success message is received by the controller. If the login process is successful a URL redirection should happen.

    I have tried to use jasmine spy objects to mock the WebService functionality but I seem to be doing something wrong which I have been unable to pinpoint.But I am currently having a problem evaluating the received message status and evaluating the URL redirection process based on that.

    AngularJS code
    ———————

    Apps.controller(‘loginCtrl’,[‘$scope’,’$location’,’WebService’,’$timeout’,,’$log’,’$http’,
    function($scope,$location,WebService,$timeout,$log,$http)
    {
    //Hardcoded values for testing
    $scope.username = ‘v@v.com’;
    $scope.password = ‘viranga123’;

    $scope.login = function()
    {
    var login = {};
    login.UserName = $scope.username;
    login.Password = $scope.password;
    WebService.loginRequest($location.host(),$location.port(),”Check”,”‘?'”,login);//
    }

    //Error in Unit testing this block
    $scope.$on(‘LSuccess_msg’,function(e,msg)
    {
    if(angular.equals(msg.Status,”LOGIN_SUCCESS”))
    {
    $timeout(function ()
    {
    window.location.href = “http://”+;}, 10);
    }
    });

    }]);

    Unit test used to test controller
    —————————————-
    “use strict”;

    describe(“Controller : Login controller”, function()
    {
    var $scope, ctrl, $location, $timeout, WebServiceMock;

    beforeEach(module(‘Apps’, function($provide)
    {
    WebServiceMock = jasmine.createSpyObj(“WebService”, [“loginRequest”]);

    WebServiceMock.loginRequest.andReturn({ UserID:’1′, SessionId:’1118430′, Status:’LOGIN_SUCCESS’, EMail:’v@v.com’, FirstName:’Viranga’});

    $provide.value(“WebServiceMock”, WebServiceMock)

    }));

    beforeEach(inject(function($rootScope, $controller, $q, _$timeout_, _$location_, _WebServiceMock_)
    {
    $scope = $rootScope.$new();
    $timeout = _$timeout_;
    $location = _$location_;

    ctrl = $controller(‘loginCtrl’,{
    $scope: $scope,
    $location: $location,
    WebService: _WebServiceMock_,
    $timeout: $timeout
    });

    $scope.$digest();
    }));

    it(‘should call loginRequest WebServiceMock’, function ()
    {
    $scope.username = ‘v@v.com’;
    $scope.password = ‘viranga123’;
    $scope.$digest();

    $scope.login();
    expect(WebServiceMock.loginRequest).toHaveBeenCalled();

    //Validate recieved data
    spyOn($scope, ‘$on’).andCallThrough();
    expect(result.Status).toEqual(“LOGIN_SUCCESS”);//Error here
    });
    });

    Error
    ——-
    Chrome 36.0.1985 (Windows 7) Controller : Login controller should call is loginRequest on WebService FAILED
    TypeError: Cannot read property ‘Status’ of undefined at null.

    1. You don’t seem to use promises in your service at all. At least I don’t see one in your code snippet. Instead you seem to ignore the return value to WebService.loginRequest() completely and the loginRequest() method seems to “call back” via an event, which I find very unusual.

      My example was specifically for unit testing when the service method is returning an angular promise (when you call a then() method on the return value of a service method call).

      You can find plenty of jsfiddle examples for this problem now, f.i. this one (its not mine): http://jsfiddle.net/eitanp461/vjJmY/

  4. Hello, I am very new at Unit Testing in Angular and I really would like your help if is it possible.

    I have this controller:

    angular.module(‘newradarApp’)
    .controller(‘ProfileController’, [‘$scope’, ‘$log’, ‘$auth’, ‘userRestService’,
    function ($scope, $log, $auth, userRestService) {

    /**
    * loading ui toggle ativator
    */
    $scope.userLoaded = false;
    /**
    * @returns {*} Checks (In combination with satellizer) the contextual authentication state
    */
    $scope.userIsLoggedIn = function () {
    return $auth.isAuthenticated();
    };

    //$scope.welcomeMsg = ‘Welkom, ‘;

    /**
    * Holds the instance of the user’s service
    */
    var userServiceInstance = userRestService.obtainPersonalInformation().then(function (userData) {
    $log.debug(‘::ReST-obtainPersonalInformation Got user data:’, JSON.stringify(userData));
    $scope.userName = userData.firstName;
    $scope.fullName = userData.firstName + ‘ ‘ + userData.lastName;
    $scope.picture = encodeURI(userData.profilePicture);
    $scope.userLoaded = true;
    });

    }]);

    How can I test it properly please? What should I do first?

    Thank you very much!!!

  5. For people reading this tutorial after the Jasmine 2.0 release:
    the “andReturn” method is now called “.and.returnValue” (same goes for other and* methods).

Leave a Reply

Your email address will not be published. Required fields are marked *

*