问题
I have the following jasmine test written with ng-describe
, run with karma
.
(I am using es6-promise polyfill for PhantomJS)
var myModule = angular.module('MyModule', []);
myModule.service('MyService', [function() {
return {
getItems: function() {
// this will be spied and mocked
}
};
}]);
myModule.controller('MyController', ['$scope', 'MyService',
function($scope, MyService) {
$scope.items = [];
$scope.refreshItems = function() {
MyService.getItems().then(
function ok(items) {
$scope.items = items;
console.log('OK, items.length = ' + items.length);
},
function fail(reason) {
console.error('FAIL')
}).catch(console.error.bind(console));
};
}
]);
ngDescribe({
name: "MyController test",
modules: ['MyModule'],
inject: ['MyService', '$rootScope', '$q'],
controllers: 'MyController',
tests: function(deps) {
function getPromise(val) {
return new Promise(function(resolve, reject) {
resolve(val);
});
}
it('updates $scope.items', function() {
spyOn(deps.MyService, 'getItems').and.returnValue(getPromise([4, 5, 6]));
deps.MyController.refreshItems();
deps.$rootScope.$digest();
expect(deps.MyService.getItems).toHaveBeenCalled();
expect(deps.MyController.items.length).toBe(3);
console.log("END OF TEST");
});
}
});
The test would fail because the promise is resolved too late:
LOG: 'END OF TEST'
PhantomJS 1.9.8 (Windows 7 0.0.0) MyController test updates $scope.items FAILED
Expected 0 to be 3.
at d:/git/myproject/test/controllers/ItmngtControllerTest.js:49
LOG: 'OK, items.length = 3'
PhantomJS 1.9.8 (Windows 7 0.0.0): Executed 37 of 37 (1 FAILED) (0.085 secs / 0.26 secs)
After too long investigation, I figured it that if I use $q
instead of Promise
, it will work fine.
function getPromise(val) {
var deferred = deps.$q.defer();
deferred.resolve(val);
return deferred.promise;
}
I'm wondering however, why is this the case, and can I change something in my test to use Promise
instead of $q
to make the test pass?
I've read in various places about $rootScope.$apply()
but wherever I put it, it still doesn't work for me.
回答1:
Angular $q
testing is synchronous, which is a huge advantage. Once scope $digest()
was called, the one can expect that all $q
promise chain handlers were called also.
Promises in general (including ES6 implementation) are asynchronous by design, on the other hand. Once the promise was resolved, its handlers will be called on the next tick. So pleasant Angular testing is not that synchronous anymore:
it('updates $scope.items', function (done) {
...
setTimeout(() => {
expect(deps.MyService.getItems).toHaveBeenCalled();
expect(deps.MyController.items.length).toBe(3);
console.log("END OF TEST");
done();
});
});
Promise
can be mocked for testing purposes to be synchronous, mock-promises may be used in this case.
And there is jasmine-co which is intended to make asynchronous testing in Jasmine easier (and it actually does) with ES6 generators and co
, I'm not aware how good it performs with ng-describe
.
来源:https://stackoverflow.com/questions/34228045/promise-resolved-too-late-in-angularjs-jasmine-test