我有一个返回promise的AngularJS服务。
尽管代码运行得很好,但测试给我带来了一些困难,因为承诺的“then”方法在我的单元测试中从未调用过。
常见的答案似乎是在帖子“AngularJS Promise Callback Not Trigged in JasmineJS Test”中提到的$rootScope.$apply()
。但是,如果我这样做,我的测试会尝试加载templates/home.html
,这是不期望的:
PhantomJS 1.9.7 (Linux) Controller: ScanCtrl should invoke the barcode scanner when it is available FAILED
Error: Unexpected request: GET templates/home.html
No more request expected
如果我未能包含$rootScope.$apply()
,那么承诺的“then”方法永远不会被调用,并且我得到一个错误,我的间谍没有被按预期调用:
PhantomJS 1.9.7 (Linux) Controller: ScanCtrl should invoke the barcode scanner when it is available FAILED
Expected spy go to have been called with [ 'enterQuantity', { barcodeId : '888888888888' } ] but it was never called.
所以我的问题是 - 我应该如何让这些测试工作?有没有办法避免调用$rootScope.$apply()
?或者,我是否需要找到一种方法让我的代码在调用templates/home.html
时不会尝试转到$rootScope.$apply()
?
服务
.factory('BarcodeScannerService', ['$q', function ($q) {
return {
scanBarcode: function () {
var deferred = $q.defer();
plugins.barcodeScanner.scan(
function (result) {
console.log("We got a barcode\n" +
"Result: " + result.text + "\n" +
"Format: " + result.format + "\n" +
"Cancelled: " + result.cancelled);
deferred.resolve({"error": false, "barcode": result.text});
},
function (error) {
deferred.resolve({"error": true});
});
return deferred.promise;
}
};
}]
)
单元测试
it('should invoke the barcode scanner when it is available', function () {
inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
$rootScopeHolder = $rootScope;
var deferred = $q.defer();
barcodeScannerServiceMock.scanBarcode = jasmine.createSpy('scanBarcode').andReturn(deferred.promise);
deferred.resolve({"error": false, "barcode": fakeBarcode2});
barcodeScannerServiceMock.isAvailable = jasmine.createSpy('isAvailable').andReturn(true);
//$scope, $timeout, Items, $state, SubmitCartService, $window
ScanCtrl = $controller('ScanCtrl', {
$scope: scope,
$window: windowMock,
Items: itemMock,
BarcodeScannerService: barcodeScannerServiceMock,
$state: stateMock
});
});
expect(barcodeScannerServiceMock.isAvailable).toHaveBeenCalled();
expect(barcodeScannerServiceMock.scanBarcode).toHaveBeenCalled();
//$rootScopeHolder.$apply();
expect(stateMock.go).toHaveBeenCalledWith('enterQuantity', { barcodeId: fakeBarcode2 });
});
受测试控制器
.controller('ScanCtrl', function ($scope, $timeout, $ionicModal, $state, BarcodeScannerService, $window) {
$scope.handleBarcodeScanError = function () {
var r = $window.confirm("Scanning failed. Try again?");
if (r === true) {
$state.go('scan');
}
else {
$state.go('home');
}
};
console.log("Scanner Avaialble?" + BarcodeScannerService.isAvailable());
if (BarcodeScannerService.isAvailable() === true) {
var barcodeResult = {};
BarcodeScannerService.scanBarcode()
.then(function(result){
barcodeResult = result;
if (barcodeResult.error === false) {
$state.go('enterQuantity', {barcodeId: barcodeResult.barcode});
}
else {
$scope.handleBarcodeScanError();
}
}, function(error){
$scope.handleBarcodeScanError();
});
}
//else, if barcode scanner is not available ask them to key it in
else {
var tempBarcode = $window.prompt('Enter barcode:');
$state.go('enterQuantity', {barcodeId: tempBarcode});
}
}
)
完整代码: https://github.com/derekdata/barcode-cart-builder/
控制器:www/js/app.js
服务:www/js/services/services.js
测试:www_test/spec/controllers/ScanCtrlTest.js
提前感谢你能给我的任何见解。
答案 0 :(得分:6)
您似乎误解了该解决方案。你需要调用$ rootScope。$ digest()而不是$ rootScope。$ apply(); $ digest循环是导致promises检查是否满足的原因。此外,您只需要为当前作用域调用$ digest()循环,因此您的实际调用将是作用域。$ digest(),并放在断言上方。
答案 1 :(得分:4)
除了使用$rootScope.$digest
或$rootScope.$apply
之外,还可以使用Q库(https://github.com/kriskowal/q)作为替代品,例如:
beforeEach(function () {
module('Module', function ($provide) {
$provide.value('$q', Q);
});
});
这种方式可以在$ digest循环之外解析/拒绝承诺。