diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 1b41666f..a13696be 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -117,7 +117,8 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveBeenCalledWith', 'toMatch', 'toThrow', - 'toThrowError' + 'toThrowError', + 'toThrowMatching', ], matchers = {}; @@ -3842,8 +3843,6 @@ getJasmineRequireObj().toThrowError = function(j$) { if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); - } else if (j$.isFunction_(arguments[1])) { - return predicateMatcher(arguments[1]); } else { return exactMatcher(arguments[1], null); } @@ -3860,30 +3859,12 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function predicateMatcher(predicate) { - return { - match: function(thrown) { - if (predicate(thrown)) { - return pass(function() { - return 'Expected function not to throw an exception matching a predicate.'; - }); - } else { - return fail(function() { - return 'Expected function to throw an exception matching a predicate, ' + - 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message) + '.'; - }); - } - } - }; - } - function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); + throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } @@ -3982,6 +3963,75 @@ getJasmineRequireObj().toThrowError = function(j$) { return toThrowError; }; +getJasmineRequireObj().toThrowMatching = function(j$) { + var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); + + /** + * {@link expect} a function to `throw` something matching a predicate. + * @function + * @name matchers#toThrowMatching + * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. + * @example + * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); + */ + function toThrowMatching() { + return { + compare: function(actual, predicate) { + var thrown; + + if (typeof actual !== 'function') { + throw new Error(usageError('Actual is not a Function')); + } + + if (typeof predicate !== 'function') { + throw new Error(usageError('Predicate is not a Function')); + } + + try { + actual(); + return fail('Expected function to throw an exception.'); + } catch (e) { + thrown = e; + } + + if (predicate(thrown)) { + return pass('Expected function not to throw an exception matching a predicate.'); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + thrownDescription(thrown) + '.'; + }); + } + } + }; + } + + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message); + } else { + return j$.pp(thrown); + } + } + + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + + return toThrowMatching; +}; + getJasmineRequireObj().MockDate = function() { function MockDate(global) { var self = this; diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index c88bbf57..148997ce 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -7,7 +7,7 @@ describe("toThrowError", function() { }).toThrowError(/Actual is not a Function/); }); - it("throws an error when the expected is not an Error, string, RegExp, or function", function() { + it("throws an error when the expected is not an Error, string, or RegExp", function() { var matcher = jasmineUnderTest.matchers.toThrowError(), fn = function() { throw new Error("foo"); @@ -15,7 +15,7 @@ describe("toThrowError", function() { expect(function() { matcher.compare(fn, 1); - }).toThrowError(/Expected is not an Error, string, RegExp, or Function./); + }).toThrowError(/Expected is not an Error, string, or RegExp./); }); it("throws an error when the expected error type is not an Error", function() { @@ -312,32 +312,4 @@ describe("toThrowError", function() { expect(result.pass).toBe(false); expect(result.message()).toEqual("Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."); }); - - it("passes if the argument is a function that returns true when called with the error", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), - predicate = function(e) { return e.message === "nope" }, - fn = function() { - throw new TypeError("nope"); - }, - result; - - result = matcher.compare(fn, predicate); - - expect(result.pass).toBe(true); - expect(result.message()).toEqual("Expected function not to throw an exception matching a predicate."); - }); - - it("fails if the argument is a function that returns false when called with the error", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), - predicate = function(e) { return e.message === "oh no" }, - fn = function() { - throw new TypeError("nope"); - }, - result; - - result = matcher.compare(fn, predicate); - - expect(result.pass).toBe(false); - expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw TypeError with message 'nope'."); - }); }); diff --git a/spec/core/matchers/toThrowMatchingSpec.js b/spec/core/matchers/toThrowMatchingSpec.js new file mode 100644 index 00000000..1987b73d --- /dev/null +++ b/spec/core/matchers/toThrowMatchingSpec.js @@ -0,0 +1,73 @@ +describe("toThrowMatching", function() { + it("throws an error when the actual is not a function", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(); + + expect(function() { + matcher.compare({}, function() { return true; }); + }).toThrowError(/Actual is not a Function/); + }); + + it("throws an error when the expected is not a function", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + throw new Error("foo"); + }; + + expect(function() { + matcher.compare(fn, 1); + }).toThrowError(/Predicate is not a Function/); + }); + + it("fails if actual does not throw at all", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + return true; + }, + result; + + result = matcher.compare(fn, function() { return true; }); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected function to throw an exception."); + }); + + it("fails with the correct message if thrown is a falsy value", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + throw undefined; + }, + result; + + result = matcher.compare(fn, function() { return false; }); + expect(result.pass).toBe(false); + expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw undefined."); + }); + + it("passes if the argument is a function that returns true when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + predicate = function(e) { return e.message === "nope" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(true); + expect(result.message).toEqual("Expected function not to throw an exception matching a predicate."); + }); + + it("fails if the argument is a function that returns false when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + predicate = function(e) { return e.message === "oh no" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(false); + expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw TypeError with message 'nope'."); + }); +}); diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js index 0d365617..2a97b5dc 100644 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -23,7 +23,8 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveBeenCalledWith', 'toMatch', 'toThrow', - 'toThrowError' + 'toThrowError', + 'toThrowMatching', ], matchers = {}; diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index ebbc4d3e..2024d2a3 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -56,8 +56,6 @@ getJasmineRequireObj().toThrowError = function(j$) { if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); - } else if (j$.isFunction_(arguments[1])) { - return predicateMatcher(arguments[1]); } else { return exactMatcher(arguments[1], null); } @@ -74,30 +72,12 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function predicateMatcher(predicate) { - return { - match: function(thrown) { - if (predicate(thrown)) { - return pass(function() { - return 'Expected function not to throw an exception matching a predicate.'; - }); - } else { - return fail(function() { - return 'Expected function to throw an exception matching a predicate, ' + - 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message) + '.'; - }); - } - } - }; - } - function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); + throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } diff --git a/src/core/matchers/toThrowMatching.js b/src/core/matchers/toThrowMatching.js new file mode 100644 index 00000000..bcc37166 --- /dev/null +++ b/src/core/matchers/toThrowMatching.js @@ -0,0 +1,68 @@ +getJasmineRequireObj().toThrowMatching = function(j$) { + var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); + + /** + * {@link expect} a function to `throw` something matching a predicate. + * @function + * @name matchers#toThrowMatching + * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. + * @example + * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); + */ + function toThrowMatching() { + return { + compare: function(actual, predicate) { + var thrown; + + if (typeof actual !== 'function') { + throw new Error(usageError('Actual is not a Function')); + } + + if (typeof predicate !== 'function') { + throw new Error(usageError('Predicate is not a Function')); + } + + try { + actual(); + return fail('Expected function to throw an exception.'); + } catch (e) { + thrown = e; + } + + if (predicate(thrown)) { + return pass('Expected function not to throw an exception matching a predicate.'); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + thrownDescription(thrown) + '.'; + }); + } + } + }; + } + + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message); + } else { + return j$.pp(thrown); + } + } + + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + + return toThrowMatching; +};