mock angular service/promise in a karma/jasmine test
So you want to test, if your service responses as expected? Then, this is something you would rather test on the service. Unit test promise based methods could look like this:
var mapService, $httpBackend, $q, $rootScope;
beforeEach(inject(function (_mapService_, _$httpBackend_, _$q_, _$rootScope_) {
mapService = mapService;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
// expect the actual request
$httpBackend.expect('GET', '/onmap/rest/map/uuid?editor=true');
// react on that request
$httpBackend.whenGET('/onmap/rest/map/uuid?editor=true').respond({
success: {
elements: [1, 2, 3]
}
});
}));
As you can see, you don't need to use $injector
, since you can inject your needed services directly. If you wanna use the correct service names throughout your tests, you can inject them with prefixed and suffixed "_", inject()
is smart enough to recognise which service you mean. We also setup the $httpBackend
mock for each it()
spec. And we set up $q
and $rootScope
for later processing.
Here's how you could test that your service method returns a promise:
it('should return a promise', function () {
expect(mapService.getMapUuid('uuid', true).then).toBeDefined();
});
Since a promise always has a .then()
method, we can check for this property to see if it's a promise or not (of course, other objects could have this method too).
Next you can test of the promise you get resolves with the proper value. You can do that setting up a deferred
that you explicitly resolve.
it('should resolve with [something]', function () {
var data;
// set up a deferred
var deferred = $q.defer();
// get promise reference
var promise = deferred.promise;
// set up promise resolve callback
promise.then(function (response) {
data = response.success;
});
mapService.getMapUuid('uuid', true).then(function(response) {
// resolve our deferred with the response when it returns
deferred.resolve(response);
});
// force `$digest` to resolve/reject deferreds
$rootScope.$digest();
// make your actual test
expect(data).toEqual([something]);
});
Hope this helps!
clement
Updated on April 26, 2020Comments
-
clement about 4 years
I'm trying to write a karma/jasmine test and I would like some explanations about how mocks are working on a service which is returning a promise. I explain my situation :
I have a controller in which I do the following call :
mapService.getMapByUuid(mapUUID, isEditor).then(function(datas){ fillMapDatas(datas); }); function fillMapDatas(datas){ if($scope.elements === undefined){ $scope.elements = []; } //Here while debugging my unit test, 'datas' contain the promise javascript object instead //of my real reponse. debugger; var allOfThem = _.union($scope.elements, datas.elements); ...
Here is how my service is :
(function () { 'use strict'; var serviceId = 'mapService'; angular.module('onmap.map-module.services').factory(serviceId, [ '$resource', 'appContext', 'restHello', 'restMap', serviceFunc]); function serviceFunc($resource, appContext, restHello, restMap) { var Maps = $resource(appContext+restMap, {uuid: '@uuid', editor: '@editor'}); return{ getMapByUuid: function (uuid, modeEditor) { var maps = Maps.get({'uuid' : uuid, 'editor': modeEditor}); return maps.$promise; } }; } })();
And finally, here is my unit test :
describe('Map controller', function() { var $scope, $rootScope, $httpBackend, $timeout, createController, MapService, $resource; beforeEach(module('onmapApp')); beforeEach(inject(function($injector) { $httpBackend = $injector.get('$httpBackend'); $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); var $controller = $injector.get('$controller'); createController = function() { return $controller('maps.ctrl', { '$scope': $scope }); }; })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); var response = {"elements":[1,2,3]}; it('should allow user to get a map', function() { var controller = createController(); $httpBackend.expect('GET', '/onmap/rest/map/MY-UUID?editor=true') .respond({ "success": response }); // hope to call /onmap/rest/map/MY-UUID?editor=true url and hope to have response as the fillMapDatas parameter $scope.getMapByUUID('MY-UUID', true); $httpBackend.flush(); }); });
What I really want to do is to have my response object ( {"elements:...}) as the datas parameter of the fillMapDatas function. I don't understand how to mock all the service things (service, promise, then)
-
clement almost 10 yearsThanks, that seems very good. Just one question (I'm a very beginer with angular) : is it a good way to return the promise in my service and use "then" function in my controller as I did please ? I'm not very sure about that.
-
Pascal Precht almost 10 yearsWell, since it's an asynchronous task, it's totally fine to use promise for your service method. This is how you actually should do things that are asynchronous.
-
clement almost 10 yearsSorry but actually I don't understand the last "it" function. On expect(data).toEqual([something]); "data" is always undefined. If i put a "debugger" instruction in promise.then function, unit test is never goes on it. Do you have any idea please ?
-
HankCa over 9 yearsGreat work, thanks for that. It requires a few corrections to be complete. 1. Your example is missing
$httpBackend.flush();
(it is in the question); 2.mapService.getMapUuid('uuid', true).then(response) {
should readmapService.getMapUuid('uuid', true).then(function(response) {