0

Here's the toy service which I'm testing. As you can see there are three methods, method1, method2 and method3. Each of the three methods does the same thing: each returns a rejected promise.

angular.module('MyModule', []).service('MyService', ['$q', function($q) {
  this.method1 = function() {
    return $q.reject(Error('rejected1'));
  };

  this.method2 = function() {
    return new $q(function(resolve, reject) {
      reject(Error('rejected2'));
    });
  };

  this.method3 = function() {
    return $q.resolve().then(function() {
      throw Error('rejected3');
    });
  };
}]);

And here is my Jasmine test for this service. As you can see, there are three nearly identical test cases:

describe('MyModule', function() {
  var $rootScope, MyService;
  beforeEach(function() {
    angular.mock.module('MyModule');
    angular.mock.inject(function(_$rootScope_, _MyService_) {
      $rootScope = _$rootScope_;
      MyService = _MyService_;
    });
  });

  // This test passes
  it('MyService.method1 rejects', function() {
    MyService.method1().catch(function(e) {
      expect(e.message).toBe('rejected1');
    });
    $rootScope.$digest();
  });

  // This test passes
  it('MyService.method2 rejects', function() {
    MyService.method2().catch(function(e) {
      expect(e.message).toBe('rejected2');
    });
    $rootScope.$digest();
  });

  // This test fails
  it('MyService.method3 rejects', function() {
    MyService.method3().catch(function(e) {
      expect(e.message).toBe('rejected3');
    });
    $rootScope.$digest();
  });
});

The third of these tests fails:

PhantomJS 2.1.1 (Windows 7 0.0.0): Executed 0 of 3 SUCCESS (0 secs / 0 secs)
PhantomJS 2.1.1 (Windows 7 0.0.0): Executed 1 of 3 SUCCESS (0 secs / 0.02 secs)
PhantomJS 2.1.1 (Windows 7 0.0.0): Executed 2 of 3 SUCCESS (0 secs / 0.022 secs)
PhantomJS 2.1.1 (Windows 7 0.0.0) MyModule MyService.method3 rejects FAILED
    Error: rejected3 in C:/.../src/my-module.spec.js (line 26)
    C:/.../src/my-module.spec:26:33
    processQueue@C:/.../node_modules/angular/angular.js:16698:30
    C:/.../node_modules/angular/angular.js:16714:39
    $eval@C:/.../node_modules/angular/angular.js:17996:28
    $digest@C:/.../node_modules/angular/angular.js:17810:36
    C:/.../src/my-module.spec.spec.js:62:25
PhantomJS 2.1.1 (Windows 7 0.0.0): Executed 3 of 3 (1 FAILED) (0 secs / 0.024 secs)
  • Why does this third case behave differently?
  • How should I test method3?
qntm
  • 4,147
  • 4
  • 27
  • 41
  • In short, I think this is a peculiarity of `angular.$q`: Prior to v1.6, thrown exceptions would also go to the browser's exception handling, which I think is causing the failure in Jasmine (based on the "Error:"). Suggesting [this question](http://stackoverflow.com/questions/23324942/angularjs-promises-rethrow-caught-exceptions) as a dupe but I can retract if this isn't related. – Jeff Bowman Apr 21 '17 at 22:40
  • Possible duplicate of [AngularJS - Promises rethrow caught exceptions](http://stackoverflow.com/questions/23324942/angularjs-promises-rethrow-caught-exceptions) – Jeff Bowman Apr 21 '17 at 22:40
  • I think that answers my first question - it's a defect in AngularJS - but not my second. – qntm Apr 21 '17 at 23:16
  • And you're not able to either [upgrade to Angular 1.6](http://stackoverflow.com/a/42250798/1426891) or [wrap your `$q` code in a try/catch](http://stackoverflow.com/a/23325589/1426891)? I bet you could even create a `safeThen(thenHandler)` function like `try { return Promise.resolve(thenHandler(input)); } catch (e) { return $q.reject(e); }` to encapsulate the try blocks. – Jeff Bowman Apr 22 '17 at 00:42
  • I also wouldn't rush to call it a defect--it's surprising, and it's different than the browser-native Promise implementation, but as the top answer in the dupe question reflects: The Promises A+ spec only insists that [the promise be rejected](https://promisesaplus.com/#the-then-method) and not that the implementation must keep the exception away from any other reporting or handling. – Jeff Bowman Apr 22 '17 at 00:47

0 Answers0