diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 492f70ef..c064faa1 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3335,6 +3335,95 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }; +getJasmineRequireObj().toBeRejectedWithError = function(j$) { + /** + * Expect a promise to be rejected with a value matched to the expected + * @function + * @async + * @name async-matchers#toBeRejectedWithError + * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. + * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` + * @example + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, 'Error message'); + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, /Error message/); + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError); + * await expectAsync(aPromise).toBeRejectedWithError('Error message'); + * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); + */ + return function toBeRejectedWithError() { + return { + compare: function(actualPromise, arg1, arg2) { + var expected = getExpectedFromArgs(arg1, arg2); + + return actualPromise.then( + function() { + return { + pass: false, + message: 'Expected a promise to be rejected but it was resolved.' + }; + }, + function(actualValue) { return matchError(actualValue, expected); } + ); + } + }; + }; + + function matchError(actual, expected) { + if (!j$.isError_(actual)) { + return fail(expected, 'rejected with ' + j$.pp(actual)); + } + + if (!(actual instanceof expected.error)) { + return fail(expected, 'rejected with type ' + j$.fnNameFor(actual.constructor)); + } + + var actualMessage = actual.message; + + if (actualMessage === expected.message || typeof expected.message === 'undefined') { + return pass(expected); + } + + if (expected.message instanceof RegExp && expected.message.test(actualMessage)) { + return pass(expected); + } + + return fail(expected, 'rejected with ' + j$.pp(actual)); + } + + function pass(expected) { + return { + pass: true, + message: 'Expected a promise not to be rejected with ' + expected.printValue + ', but it was.' + }; + } + + function fail(expected, message) { + return { + pass: false, + message: 'Expected a promise to be rejected with ' + expected.printValue + ' but it was ' + message + '.' + }; + } + + + function getExpectedFromArgs(arg1, arg2) { + var error, message; + + if (typeof arg1 === 'function' && j$.isError_(arg1.prototype)) { + error = arg1; + message = arg2; + } else { + error = Error; + message = arg1; + } + + return { + error: error, + message: message, + printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + j$.pp(message)) + }; + } +}; + getJasmineRequireObj().toBeResolved = function(j$) { /** * Expect a promise to be resolved. @@ -4000,7 +4089,8 @@ getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', - 'toBeRejectedWith' + 'toBeRejectedWith', + 'toBeRejectedWithError' ], matchers = {}; diff --git a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js new file mode 100644 index 00000000..6ec2a31a --- /dev/null +++ b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js @@ -0,0 +1,141 @@ +describe('#toBeRejectedWithError', function () { + it('passes when Error type matches', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new TypeError('foo')); + + return matcher.compare(actual, TypeError).then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with TypeError, but it was.' + })); + }); + }); + + it('passes when Error type and message matches', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new TypeError('foo')); + + return matcher.compare(actual, TypeError, 'foo').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with TypeError: \'foo\', but it was.' + })); + }); + }); + + it('passes when Error message matches a string', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error('foo')); + + return matcher.compare(actual, 'foo').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with Error: \'foo\', but it was.' + })); + }); + }); + + it('passes when Error message matches a RegExp', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error('foo')); + + return matcher.compare(actual, /foo/).then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with Error: /foo/, but it was.' + })); + }); + }); + + it('passes when Error message is empty', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error()); + + return matcher.compare(actual, '').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with Error: \'\', but it was.' + })); + }); + }); + + it('passes when no arguments', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error()); + + return matcher.compare(actual, void 0).then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: true, + message: 'Expected a promise not to be rejected with Error, but it was.' + })); + }); + }); + + it('fails when resolved', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.resolve(new Error('foo')); + + return matcher.compare(actual, 'foo').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: false, + message: 'Expected a promise to be rejected but it was resolved.' + })); + }); + }); + + it('fails when rejected with non Error type', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject('foo'); + + return matcher.compare(actual, 'foo').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: false, + message: 'Expected a promise to be rejected with Error: \'foo\' but it was rejected with \'foo\'.' + })); + }); + }); + + it('fails when Error type mismatches', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error('foo')); + + return matcher.compare(actual, TypeError, 'foo').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: false, + message: 'Expected a promise to be rejected with TypeError: \'foo\' but it was rejected with type Error.' + })); + }); + }); + + it('fails when Error message mismatches', function () { + jasmine.getEnv().requirePromises(); + + var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + actual = Promise.reject(new Error('foo')); + + return matcher.compare(actual, 'bar').then(function (result) { + expect(result).toEqual(jasmine.objectContaining({ + pass: false, + message: 'Expected a promise to be rejected with Error: \'bar\' but it was rejected with Error: foo.' + })); + }); + }); +}); diff --git a/src/core/matchers/async/toBeRejectedWithError.js b/src/core/matchers/async/toBeRejectedWithError.js new file mode 100644 index 00000000..88f1c11f --- /dev/null +++ b/src/core/matchers/async/toBeRejectedWithError.js @@ -0,0 +1,88 @@ +getJasmineRequireObj().toBeRejectedWithError = function(j$) { + /** + * Expect a promise to be rejected with a value matched to the expected + * @function + * @async + * @name async-matchers#toBeRejectedWithError + * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. + * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` + * @example + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, 'Error message'); + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, /Error message/); + * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError); + * await expectAsync(aPromise).toBeRejectedWithError('Error message'); + * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); + */ + return function toBeRejectedWithError() { + return { + compare: function(actualPromise, arg1, arg2) { + var expected = getExpectedFromArgs(arg1, arg2); + + return actualPromise.then( + function() { + return { + pass: false, + message: 'Expected a promise to be rejected but it was resolved.' + }; + }, + function(actualValue) { return matchError(actualValue, expected); } + ); + } + }; + }; + + function matchError(actual, expected) { + if (!j$.isError_(actual)) { + return fail(expected, 'rejected with ' + j$.pp(actual)); + } + + if (!(actual instanceof expected.error)) { + return fail(expected, 'rejected with type ' + j$.fnNameFor(actual.constructor)); + } + + var actualMessage = actual.message; + + if (actualMessage === expected.message || typeof expected.message === 'undefined') { + return pass(expected); + } + + if (expected.message instanceof RegExp && expected.message.test(actualMessage)) { + return pass(expected); + } + + return fail(expected, 'rejected with ' + j$.pp(actual)); + } + + function pass(expected) { + return { + pass: true, + message: 'Expected a promise not to be rejected with ' + expected.printValue + ', but it was.' + }; + } + + function fail(expected, message) { + return { + pass: false, + message: 'Expected a promise to be rejected with ' + expected.printValue + ' but it was ' + message + '.' + }; + } + + + function getExpectedFromArgs(arg1, arg2) { + var error, message; + + if (typeof arg1 === 'function' && j$.isError_(arg1.prototype)) { + error = arg1; + message = arg2; + } else { + error = Error; + message = arg1; + } + + return { + error: error, + message: message, + printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + j$.pp(message)) + }; + } +}; diff --git a/src/core/matchers/requireAsyncMatchers.js b/src/core/matchers/requireAsyncMatchers.js index 1474c22d..faa91c02 100644 --- a/src/core/matchers/requireAsyncMatchers.js +++ b/src/core/matchers/requireAsyncMatchers.js @@ -3,7 +3,8 @@ getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', - 'toBeRejectedWith' + 'toBeRejectedWith', + 'toBeRejectedWithError' ], matchers = {};