测试AngularJS服务,它返回一个promise而不调用$ rootScope。$ apply()?

时间:2014-03-08 21:37:19

标签: javascript angularjs jasmine ionic-framework

我有一个返回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

提前感谢你能给我的任何见解。

2 个答案:

答案 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循环之外解析/拒绝承诺。