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