diff --git a/docs/03-assertions.md b/docs/03-assertions.md index ad7725f99..6714a84a9 100644 --- a/docs/03-assertions.md +++ b/docs/03-assertions.md @@ -172,7 +172,10 @@ Assert that an error is thrown. `fn` must be a function which should throw. The * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message +* `message`: the following types are valid: + * *string* - it is compared against the thrown error's message + * *regular expression* - it is matched against this message + * *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error @@ -204,7 +207,10 @@ The thrown value *must* be an error. It is returned so you can run more assertio * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message +* `message`: the following types are valid: + * *string* - it is compared against the thrown error's message + * *regular expression* - it is matched against this message + * *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error diff --git a/lib/assert.js b/lib/assert.js index 70d24d1bf..5d5477071 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -106,10 +106,15 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d }); } - if (hasOwnProperty(expectations, 'message') && typeof expectations.message !== 'string' && !(expectations.message instanceof RegExp)) { + if ( + hasOwnProperty(expectations, 'message') + && typeof expectations.message !== 'string' + && !(expectations.message instanceof RegExp) + && !(typeof expectations.message === 'function') + ) { throw new AssertionError({ assertion, - message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`, + message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`, values: [formatWithLabel('Called with:', expectations)], }); } @@ -230,6 +235,19 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s }); } + if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) { + throw new AssertionError({ + assertion, + message, + savedError, + actualStack, + values: [ + formatWithLabel(`${prefix} unexpected exception:`, actual), + formatWithLabel('Expected message to return true:', expectations.message), + ], + }); + } + if (typeof expectations.code !== 'undefined' && actual.code !== expectations.code) { throw new AssertionError({ assertion, diff --git a/test-tap/assert.js b/test-tap/assert.js index c21bbc7d9..d4c0b369c 100644 --- a/test-tap/assert.js +++ b/test-tap/assert.js @@ -899,6 +899,78 @@ test('.throws()', gather(t => { formatted: /null/, }], }); + + // Fails because the string in the message is incorrect + failsWith( + t, + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: 'my error'}, + ), + { + assertion: 'throws', + message: '', + values: [ + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to equal:', formatted: /my error/}, + ], + }, + ); + + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: 'error'})); + + // Fails because the regular expression in the message is incorrect + failsWith( + t, + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: /my error/}, + ), + { + assertion: 'throws', + message: '', + values: [ + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to match:', formatted: /my error/}, + ], + }, + ); + + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: /error/})); + + // Fails because the function in the message returns false + failsWith( + t, + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: () => false}, + ), + { + assertion: 'throws', + message: '', + values: [ + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to return true:', formatted: /Function/}, + ], + }, + ); + + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: () => true})); })); test('.throws() returns the thrown error', t => { @@ -1066,7 +1138,7 @@ test('.throws() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throws(() => {}, {message: null}), { assertion: 'throws', - message: 'The `message` property of the second argument to `t.throws()` must be a string or regular expression', + message: 'The `message` property of the second argument to `t.throws()` must be a string, regular expression or a function', values: [{label: 'Called with:', formatted: /message: null/}], }); @@ -1136,7 +1208,7 @@ test('.throwsAsync() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throwsAsync(() => {}, {message: null}), { assertion: 'throwsAsync', - message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string or regular expression', + message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string, regular expression or a function', values: [{label: 'Called with:', formatted: /message: null/}], }, {expectBoolean: false}); diff --git a/types/assertions.d.ts b/types/assertions.d.ts index ce5a97a14..0d2c8506b 100644 --- a/types/assertions.d.ts +++ b/types/assertions.d.ts @@ -12,7 +12,7 @@ export type ThrowsExpectation = { is?: Error; /** The thrown error must have a message that equals the given string, or matches the regular expression. */ - message?: string | RegExp; + message?: string | RegExp | ((message: string) => boolean); /** The thrown error must have a name that equals the given string. */ name?: string;