I'm pretty new to Angular and I am working to test an Angular service that runs off an API level service which wraps a bunch of calls to a REST service. Because it is working with HTTP requests, both parts of the service are working with promises and it seems to work fine, but I am having trouble getting any tests of promised behaviour working.
The relevant part of my service code ( grossly simplified ) looks like this:
angular.module('my.info')
.service('myInfoService', function (infoApi, $q) {
infoLoaded: false,
allInfo: [],
getInfo: function () {
var defer = $q.defer();
if (infoLoaded) {
defer.resolve(allInfo);
} else {
infoApi.getAllInfo().then(function (newInfo) {
allInfo = newInfo;
infoLoaded = true;
defer.resolve(allInfo);
});
}
return defer.promise;
}
});
When I am setting up my mock I have something like this:
describe("Info Service ",
function() {
var infoService, infoRequestApi, $q;
beforeEach(module("my.info"));
beforeEach(function() {
module(function($provide) {
infoRequestApi = {
requestCount: 0,
getAllInfo: function() {
var defer = $q.defer();
this.requestCount++;
defer.resolve( [ "info 1", "info 2" ] );
return defer.promise;
}
};
$provide.value("infoApi", infoRequestApi);
});
inject(function( _myInfoService_, _$q_ ) {
infoService = _myInfoService_,
$q = _$q_;
});
});
it("should not fail in the middle of a test", function(done) {
infoService.getInfo().then( function(infoResult) {
// expectation checks.
expect( true ).toBeTrue();
}).finally(done);
});
});
Any synchronous tests pass fine, but when I try to run any tests like this I get a message saying: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
It seems as though something about the way that Angular.Mocks handles the deferred result is causing it to fail. When I step through the test, the mock object's defer
variable is being set correctly, but the then
statement in the service is never called. Where am I going wrong?
答案 0 :(得分:3)
Short Answer
You have to kick off a digest cycle. You can do that with $rootScope.$apply()
.
it("should not fail in the middle of a test", inject(function($rootScope) {
var actualResult = null;
infoService.getInfo()
.then(function(infoResult) { actualResult = infoResult; });
$rootScope.$apply();
expect(actualResult).toEqual(["info 1", "info 2"]);
}));
Note
I wouldn't try to create a fake infoRequestApi
service the way you are doing it. You should be injecting that service as well, and spying on its functions. For instance, something like this:
it("should not fail in the middle of a test", inject(function($rootScope, infoApi, $q) {
var deferred = $q.defer();
spyOn(infoApi, 'getAllInfo').and.returnValue(deferred.promise);
var actualResult = null;
var expectedResult = ["info 1", "info 2"];
infoService.getInfo()
.then(function(infoResult) { actualResult = infoResult; });
deferred.resolve(expectedResult);
$rootScope.$apply();
expect(actualResult).toBe(expectedResult);
}));
Refactored
Also, your code can be simplified a bit (untested, but close to what I would expect to see).
angular.module('my.info')
.service('myInfoService', function (infoApi, $q) {
return {
infoLoaded: false,
allInfo: [],
getInfo: function () {
var self = this;
return this.infoLoaded ? $q.resolve(this.allInfo) :
infoApi.getAllInfo().then(function (newInfo) {
self.allInfo = newInfo;
self.infoLoaded = true;
});
}
};
});
describe("Info Service ", function() {
var infoService, infoApi, $q, $rootScope;
beforeEach(module("my.info"));
beforeEach(inject(function( _myInfoService_, _$q_ _$rootScope_, _infoApi_) {
infoService = _myInfoService_,
$q = _$q_;
$rootScope = _$rootScope_;
infoApi = _infoApi_;
});
it("should do something", function() { // update message
var deferred = $q.defer();
spyOn(infoApi, 'getAllInfo').and.returnValue(deferred.promise);
var actualResult = null;
var expectedResult = ["info 1", "info 2"];
infoService.getInfo()
.then(function(infoResult) { actualResult = infoResult; });
deferred.resolve(expectedResult);
$rootScope.$apply();
expect(actualResult).toBe(expectedResult);
});
});