I am writing Jasmine unit tests for my app in Typescript and running them via Resharper. It is supposed to execute an action if the handler throws an exception:
describe("Q Service Test", () => {
var q: ng.IQService;
var rootScope: ng.IRootScopeService;
beforeEach(inject(($q, $rootScope) => {
q = $q;
rootScope = $rootScope;
}));
it("Caught exceptions are handled properly", () => {
var state = 'ok';
q.when(1)
.then(() => {
throw new Error("test exception");
})
.catch(() => {
state = 'error';
});
rootScope.$digest();
expect(state).toBe('error');
});
});
However, the test is failed:
Is it some strange behaviour of my testing environment / tools, or am I incorrectly using the promise mechanism itself?
You are definitely using the promise mechanism incorrectly, throwing a user-defined throw statement does not constitute to catching it as a promise rejection properly. As stated in the $q
documentation:
When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject.
They are similar but not equivalents, to catch user-defined throw statements, you should use catch
statement blocks. While $q
promises should only catch
rejected promises. Thus, in your case, returning a rejected a promise is the proper way of handling the process instead of throwing a user-defined exception.
DEMO
JAVASCRIPT
describe('Q Service Test', function() {
var $q,
$rootScope;
beforeEach(inject(function(_$q_, _$rootScope_) {
$q = _$q_;
$rootScope = _$rootScope_;
}));
it('Rejected promises are handled properly', function() {
var state = 'ok';
$q.when(1)
.then(function() {
return $q.reject('rejected');
})
.catch(function() {
state = 'error';
});
$rootScope.$digest();
expect(state).toBe('error');
});
});
UPDATE:
The reason why your code behaves this way in the browser, is because Angular's $q
implementation uses the try/catch
statement blocks in processing the promise queue. When any of the callbacks throw any errors, it catches the error itself, rejects it with the exception as a reason for rejection, afterwards it uses $exceptionHandler
to log the error. I recommend you to simply return rejected promise.
As for the reason why the unit tests act this way is because of angular-mocks
implementation of the $exceptionHandler
is different with the actual application's $exceptionHandler
. The former creates a provider with different modes, the default angular-mocks implementation uses the rethrow
mode which in turn throws the exception instead of logging it. If you want to have your unit tests behave the same way as how the default application's $exceptionHandler
then you can set the mode to 'log'
.
DEMO
JAVASCRIPT
describe('Q Service Test', function() {
var $q,
$rootScope;
beforeEach(module('ng', function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));
beforeEach(inject(function(_$q_, _$rootScope_) {
$q = _$q_;
$rootScope = _$rootScope_;
}));
it('Caught exceptions are handled properly', function() {
var state = 'ok';
$q.when(1)
.then(function() {
throw new Error();
})
.catch(function() {
state = 'error';
});
$rootScope.$digest();
expect(state).toBe('error');
});
});
The blog post Using and chaining promises in AngularJS mentions that when you throw and exception it "will also trigger Angular's registered exception handler". So my guess is that Jasmine is using Angular's exception handler to listening for exceptions.
Do you need to throw an exception or could you just do something like this:
return q.reject(new Error("test exception"));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With