Jason Jason - 6 months ago 18
AngularJS Question

How to mock Angular $q service in Jasmine test?

I am trying to test an Angular service, which has 2 dependencies, one on $q and another one on 'myService' which also has dependency on $q.

(function() {
'use strict';

angular.module('myModule').factory('myService', [
'$q',
'apiService',
function($q, apiService) {

var data = null;

function getData() {
var deferred = $q.defer();

if (data === null) {

apiService.get('url').then(function(result) {
data = result;
deferred.resolve(data);
}, function() {
deferred.reject();
});
} else {
deferred.resolve(data);
}

return deferred.promise;
}

return {
getData: getData
};
}
]);
})();


I started writing a Jasmine test as seen below, but and having issues mocking $q. I would like to inject the real version of $q instead of the mock version to 'myService' and 'apiService', but am not sure how to accomplish that.

'use strict';

describe('My service', function() {
var qSpy, apiServiceSpy;

beforeEach(module('myModule'));

beforeEach(function() {
qSpy = jasmine.createSpyObj('qSpy', ['defer']);

apiServiceSpy = jasmine.createSpyObj('apiServiceSpy', ['get']);
apiServiceSpy.get.and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});

module(function($provide) {
$provide.value('$q', qSpy);
$provide.value('apiService', apiServiceSpy);
});
});

it('should get data.', inject(function(myService) {
// Arrange

// Act
var data = myService.getData();

// Assert
expect(data).not.toBeNull();
}));
});


Edit
Here is the updated test based on the responses below. I guess my issue was I assumed I had to provide $q.

'use strict';

describe('My service', function() {
var service, apiServiceSpy;

beforeEach(module('myModule'));

beforeEach(function() {
apiServiceSpy = jasmine.createSpyObj('apiServiceSpy', ['get']);

module(function($provide) {
$provide.value('apiService', apiServiceSpy);
});
});

beforeEach(inject(function($q, myService) {
service = myService;

apiServiceSpy.get.and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
}));

it('should get data.', function() {
// Arrange

// Act
var data = service.getData();

// Assert
expect(data).not.toBeNull();
}));
});

Answer

You can use the real $q. Important to note, that you should call $scope.$apply() to resolve promises.

var service;
var $scope;
beforeEach(function() {

    angular.mock.module('app', function ($provide) {
        $provide.value('apiService', apiServiceSpy);
    });

    angular.mock.inject(function (_myService_, _$rootScope_) {
        service = _myService_;
        $scope = _$rootScope_;
    });
});

it('works like a charm', function() {
    var data;
    service.getData().then(function(d) {
        data = d;
    });
    $scope.$apply();  // resolve promise
    expect(data).toBeAwesomeData();
});