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.