How do I mock a service that returns promise in AngularJS Jasmine unit test?
Solution 1
I'm not sure why the way you did it doesn't work, but I usually do it with the spyOn
function. Something like this:
describe('Testing remote call returning promise', function() {
var myService;
beforeEach(module('app.myService'));
beforeEach(inject( function(_myService_, myOtherService, $q){
myService = _myService_;
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
}
it('can do remote call', inject(function() {
myService.makeRemoteCall()
.then(function() {
console.log('Success');
});
}));
Also remember that you will need to make a $digest
call for the then
function to be called. See the Testing section of the $q documentation.
------EDIT------
After looking closer at what you're doing, I think I see the problem in your code. In the beforeEach
, you're setting myOtherServiceMock
to a whole new object. The $provide
will never see this reference. You just need to update the existing reference:
beforeEach(inject( function(_myService_, $q){
myService = _myService_;
myOtherServiceMock.makeRemoteCallReturningPromise = function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
};
}
Solution 2
We can also write jasmine's implementation of returning promise directly by spy.
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
For Jasmine 2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(copied from comments, thanks to ccnokes)
Solution 3
describe('testing a method() on a service', function () {
var mock, service
function init(){
return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
mock = $injector.get('service_that_is_being_mocked');;
service = __serviceUnderTest_;
});
}
beforeEach(module('yourApp'));
beforeEach(init());
it('that has a then', function () {
//arrange
var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
return {
then: function (callback) {
return callback({'foo' : "bar"});
}
};
});
//act
var result = service.actionUnderTest(); // does cleverness
//assert
expect(spy).toHaveBeenCalled();
});
});
Solution 4
You can use a stubbing library like sinon to mock your service. You can then return $q.when() as your promise. If your scope object's value comes from the promise result, you will need to call scope.$root.$digest().
var scope, controller, datacontextMock, customer;
beforeEach(function () {
module('app');
inject(function ($rootScope, $controller,common, datacontext) {
scope = $rootScope.$new();
var $q = common.$q;
datacontextMock = sinon.stub(datacontext);
customer = {id:1};
datacontextMock.customer.returns($q.when(customer));
controller = $controller('Index', { $scope: scope });
})
});
it('customer id to be 1.', function () {
scope.$root.$digest();
expect(controller.customer.id).toBe(1);
});
Solution 5
using sinon
:
const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
.returns(httpPromise(200));
Known that, httpPromise
can be :
const httpPromise = (code) => new Promise((resolve, reject) =>
(code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);
Related videos on Youtube
Georgii Oleinikov
Updated on July 08, 2022Comments
-
Georgii Oleinikov almost 2 years
I have
myService
that usesmyOtherService
, which makes a remote call, returning promise:angular.module('app.myService', ['app.myOtherService']) .factory('myService', [ myOtherService, function(myOtherService) { function makeRemoteCall() { return myOtherService.makeRemoteCallReturningPromise(); } return { makeRemoteCall: makeRemoteCall }; } ])
To make a unit test for
myService
I need to mockmyOtherService
, such that itsmakeRemoteCallReturningPromise
method returns a promise. This is how I do it:describe('Testing remote call returning promise', function() { var myService; var myOtherServiceMock = {}; beforeEach(module('app.myService')); // I have to inject mock when calling module(), // and module() should come before any inject() beforeEach(module(function ($provide) { $provide.value('myOtherService', myOtherServiceMock); })); // However, in order to properly construct my mock // I need $q, which can give me a promise beforeEach(inject(function(_myService_, $q){ myService = _myService_; myOtherServiceMock = { makeRemoteCallReturningPromise: function() { var deferred = $q.defer(); deferred.resolve('Remote call result'); return deferred.promise; } }; } // Here the value of myOtherServiceMock is not // updated, and it is still {} it('can do remote call', inject(function() { myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {} .then(function() { console.log('Success'); }); }));
As you can see from the above, the definition of my mock depends on
$q
, which I have to load usinginject()
. Furthermore, injecting the mock should be happening inmodule()
, which should be coming beforeinject()
. However, the value for the mock is not updated once I change it.What is the proper way to do this?
-
dnc253 almost 10 yearsIs the error really on
myService.makeRemoteCall()
? If so, the problem is withmyService
not having themakeRemoteCall
, not anything to do with your mockedmyOtherService
. -
Georgii Oleinikov almost 10 yearsThe error is on myService.makeRemoteCall(), because myService.myOtherService is just an empty object at this point (its value was never updated by angular)
-
twDuke over 8 yearsYou add the empty object to the ioc container, after that you change the reference myOtherServiceMock to point to a new object which you spy on. Whats in the ioc container wont reflect that, as the reference is changed.
-
-
Priya Ranjan Singh over 9 yearsAnd you killed me yesterday by not showing up in results. Beautiful display of andCallFake(). Thank you.
-
Jordan Running over 9 yearsInstead of
andCallFake
you can useandReturnValue(deferred.promise)
(orand.returnValue(deferred.promise)
in Jasmine 2.0+). You need to definedeferred
before you callspyOn
, of course. -
ccnokes over 9 yearsNote to people using Jasmine 2.0, .andReturn() has been replaced by .and.returnValue. So the above example would be:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
I just killed a half hour figuring that out. -
Darren Corbett about 9 yearsThis is how I have done it in the past. Create a spy that returns a fake that mimics the "then"
-
Rob Paddock about 9 yearsCan you provide an example of the complete test you have. I have a similar problem of having a service that returns a promise, but with in it also makes a call which returns a promise !
-
Darren Corbett about 9 yearsHi Rob, not sure why you would want to mock a call that a mock makes to another service surely you would want to test that when testing that function. If the function calls you are mocking calls a service gets data then affects that data your mocked promise would return a fake affected data set, at least that's how I would do it.
-
Jim Aho almost 9 yearsHow would you call
$digest
in this case when you don't have access to the scope? -
dnc253 almost 9 years@JimAho Typically you just inject
$rootScope
and call$digest
on that. -
Jim Aho almost 9 years@dnc253 Yes thanks! I just found that out on this page: docs.angularjs.org/api/ng/service/$q. I totally had missed the integration of $q with the angular scoping mechanisms.
-
Darren Corbett almost 9 yearsThe whole point of a before each is that it is called before each test I don't know how you write your tests but personally I write multiple tests for a single function, therefore I would have a common base set up that would be called before each test. Also you may want to look up the understood meaning of anti pattern as it associates to software engineering.
-
Mark Nadig over 8 yearsI started down this path and it works great for simple scenarios. I even created a mock that simulates chaining and provides "keep"/"break" helpers to invoke the chain gist.github.com/marknadig/c3e8f2d3fff9d22da42b In more complex scenarios, this falls down however. In my case, I had a service that would conditionally return items from a cache (w/ deferred) or make a request. So, it was creating it's own promise.
-
Mark Nadig over 8 yearsThis is the approach I ended up with. However, I wrapped the construction of the promise in a helper function. It handles inject($q, $rootScope), $q.defer and then decorates the resulting promise with a "keep" method that calls resolve and $rootScope.digest.
-
Custodio over 8 yearsThis post ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy describes the usage of fake "then" throughly.
-
ATrubka almost 8 years$digest is most commonly forgotten step.
-
fodma1 over 7 yearsUsing deferred in this case is unnecessary. You can just use
$q.when()
codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong -
Admin almost 7 yearsthis is the missing piece, calling
$rootScope.$digest()
to get the promise to be resolved -
Ajay.k over 5 years@dnc253: I have an issue to get data from controller can you please look into it. Can you help me on it. stackoverflow.com/questions/53629138/…