I am using the basic karma/jasmine setup to test my Angular code. Here is my test:
var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;
describe('main controller', function() {
var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;
beforeEach(inject(function($controller, $rootScope, $q) {
scope = $rootScope.$new();
$controllerConstructor = $controller;
q = $q;
mockSuperheroData = {
getSuperheroes: function() {
deferred = q.defer();
return deferred.promise;
}
};
ctr = $controllerConstructor('MainCtrl', {$scope: scope, $location: {}, superheroService: mockSuperheroData, keys: {}});
}));
it('should set the result of getResource to scope.heroes', function() {
scope.getHeroes();
expect(scope.heroes).toBe(100);
});
}
scope.getHeroes()
calls the mockSuperheroData.getSuperheroes()
which is returning a promise. How do I force the promise to return what I want in the unit test? Where can I hook into the promise to mock out its return?
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.
Ends Soon Promises are becoming a common part of the JavaScript code. The native Promise object is already supported by all the major browsers including Chrome, Firefox, and Safari. Despite making asynchronous code simpler, dealing with promises in unit tests is a hassle.
In this case, if the promise was rejected, there would be no error, since there is no error handler in the test to check for it. But it’s clear the test should fail in that situation, as the expectation won’t run. This is definitely one of the main reasons why promises become complicated in tests.
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?
How do I force the promise to return what I want in the unit test?
Basically you will need to call resolve
on the Deferred:
deferred.resolve(100);
You can either put that directly before the return deferred.promise
or in an asynchronous setTimeout
.
var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;
describe('main controller', function() {
var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q, rootScope;
beforeEach(inject(function($controller, $rootScope, $q) {
scope = $rootScope.$new();
$controllerConstructor = $controller;
q = $q;
rootScope = $rootScope;
mockSuperheroData = {
getSuperheroes: function() {
deferred = q.defer();
return deferred.promise;
}
};
ctr = $controllerConstructor('MainCtrl', {$scope: scope, $location: {}, superheroService: mockSuperheroData, keys: {}});
}));
it('should set the result of getResource to scope.heroes', function() {
scope.getHeroes();
deferred.resolve(100);
rootScope.$apply();
expect(scope.heroes).toBe(100);
});
});
It should be mentioned that because $q is integrated with $rootScope, not only do you need to resolve the deferred object, but you also need to propagate the changes by calling $rootScope.$apply.
I wrote a blog on mocking angular promises at projectpoppycock
and I wrote a working example, with tests, at plunkr
I really like the way you're injecting the mock service as a dependency of the controller by using the $controller service, I may need to revise my way of doing this. Your way is better :) Thanks!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With