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

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.

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