最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Test AngularJS service that returns a promise without calling $rootScope.$apply()? - Stack Overflow

programmeradmin1浏览0评论

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 mon 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: /

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.

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 mon 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./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.

Share Improve this question edited May 23, 2017 at 12:10 CommunityBot 11 silver badge asked Mar 8, 2014 at 21:37 derekdataderekdata 731 silver badge5 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 6

It appears that you have misread that solution. You need to call $rootScope.$digest() and not $rootScope.$apply(); The $digest cycle is what causes promises to check to see if they are fulfilled. Also, you need only call the $digest() cycle for the current scope, so your actual call would be scope.$digest(), and placed above your assertions.

Other than using $rootScope.$digest or $rootScope.$apply it's also possible to use the Q library (https://github./kriskowal/q) as a drop-in replacement e.g:

beforeEach(function () {
    module('Module', function ($provide) {
        $provide.value('$q', Q); 
    });
});

This way promises can be resolved/rejected outside of the $digest cycle.

发布评论

评论列表(0)

  1. 暂无评论