diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index d642adc5..cfd1d36a 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3734,13 +3734,16 @@ getJasmineRequireObj().toThrowError = function(j$) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } - return conditionalMatcher(expected, errorType); + return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; + if (isAnErrorType(arguments[1])) { - return conditionalMatcher(null, arguments[1]); + return exactMatcher(null, arguments[1]); + } else if (j$.isFunction_(arguments[1])) { + return predicateMatcher(arguments[1]); } else { - return conditionalMatcher(arguments[1], null); + return exactMatcher(arguments[1], null); } } else { return anyMatcher(); @@ -3755,12 +3758,30 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function conditionalMatcher(expected, errorType) { + 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, or RegExp.')); + throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); } } diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index 14726464..c7750e80 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, or RegExp", function() { + it("throws an error when the expected is not an Error, string, RegExp, or function", 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, or RegExp./); + }).toThrowError(/Expected is not an Error, string, RegExp, or Function./); }); it("throws an error when the expected error type is not an Error", function() { @@ -312,4 +312,32 @@ 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/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index d6e772a1..ebbc4d3e 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -50,13 +50,16 @@ getJasmineRequireObj().toThrowError = function(j$) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } - return conditionalMatcher(expected, errorType); + return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; + if (isAnErrorType(arguments[1])) { - return conditionalMatcher(null, arguments[1]); + return exactMatcher(null, arguments[1]); + } else if (j$.isFunction_(arguments[1])) { + return predicateMatcher(arguments[1]); } else { - return conditionalMatcher(arguments[1], null); + return exactMatcher(arguments[1], null); } } else { return anyMatcher(); @@ -71,12 +74,30 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function conditionalMatcher(expected, errorType) { + 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, or RegExp.')); + throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); } }