I have an AngularJS service that returns a promise.
Though the code works perfectly well, the test is giving me some difficulty, as the "then" method of the promise is never called in my unit test.
The common answer seems to be call $rootScope.$apply()
as mentioned in the post " AngularJS Promise Callback Not Trigged in JasmineJS Test ". However, if I do this, my test tries to go load templates/home.html
, which is not expected:
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
If I fail to include $rootScope.$apply()
, the "then" method of the promise is never called and I get an error that my spy is not being called as expected:
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.
So my question is - how should I approach getting these tests to work? Is there any way to avoid calling $rootScope.$apply()
? Or, do I need to figure out a way to have my code not try to go to templates/home.html
when $rootScope.$apply()
is called?
Service
.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;
}
};
}]
)
Unit Test
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 Under Test
.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});
}
}
)
Full code here: https://github.com/derekdata/barcode-cart-builder/
Controller: www/js/app.js
Service: www/js/services/services.js
Test: www_test/spec/controllers/ScanCtrlTest.js
Thanks in advance for any insight you can give me.