Georgii Oleinikov Georgii Oleinikov - 21 days ago 4
AngularJS Question

How do I mock a service that returns promise in Angularjs Jasmine unit test?

I have myService that uses myOtherService, 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 mock
myOtherService
, such that its
makeRemoteCallReturningPromise()
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 using
inject()
. Furthermore, injecting the mock should be happening in
module()
, which should be coming before
inject()
. However, the value for mock is not updated once I change it.

What is the proper way to do this?

Answer

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;   
    };
  }