Skip to content

Commit e387cba

Browse files
authored
Support message testing function in throws & throwsAsync assertions
1 parent f5caa8f commit e387cba

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed

docs/03-assertions.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,10 @@ Assert that an error is thrown. `fn` must be a function which should throw. The
172172

173173
* `instanceOf`: a constructor, the thrown error must be an instance of
174174
* `is`: the thrown error must be strictly equal to `expectation.is`
175-
* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message
175+
* `message`: the following types are valid:
176+
* *string* - it is compared against the thrown error's message
177+
* *regular expression* - it is matched against this message
178+
* *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed
176179
* `name`: the expected `.name` value of the thrown error
177180
* `code`: the expected `.code` value of the thrown error
178181

@@ -204,7 +207,10 @@ The thrown value *must* be an error. It is returned so you can run more assertio
204207

205208
* `instanceOf`: a constructor, the thrown error must be an instance of
206209
* `is`: the thrown error must be strictly equal to `expectation.is`
207-
* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message
210+
* `message`: the following types are valid:
211+
* *string* - it is compared against the thrown error's message
212+
* *regular expression* - it is matched against this message
213+
* *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed
208214
* `name`: the expected `.name` value of the thrown error
209215
* `code`: the expected `.code` value of the thrown error
210216

lib/assert.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,15 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d
106106
});
107107
}
108108

109-
if (hasOwnProperty(expectations, 'message') && typeof expectations.message !== 'string' && !(expectations.message instanceof RegExp)) {
109+
if (
110+
hasOwnProperty(expectations, 'message')
111+
&& typeof expectations.message !== 'string'
112+
&& !(expectations.message instanceof RegExp)
113+
&& !(typeof expectations.message === 'function')
114+
) {
110115
throw new AssertionError({
111116
assertion,
112-
message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`,
117+
message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`,
113118
values: [formatWithLabel('Called with:', expectations)],
114119
});
115120
}
@@ -230,6 +235,19 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
230235
});
231236
}
232237

238+
if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) {
239+
throw new AssertionError({
240+
assertion,
241+
message,
242+
savedError,
243+
actualStack,
244+
values: [
245+
formatWithLabel(`${prefix} unexpected exception:`, actual),
246+
formatWithLabel('Expected message to return true:', expectations.message),
247+
],
248+
});
249+
}
250+
233251
if (typeof expectations.code !== 'undefined' && actual.code !== expectations.code) {
234252
throw new AssertionError({
235253
assertion,

test-tap/assert.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,78 @@ test('.throws()', gather(t => {
899899
formatted: /null/,
900900
}],
901901
});
902+
903+
// Fails because the string in the message is incorrect
904+
failsWith(
905+
t,
906+
() =>
907+
assertions.throws(
908+
() => {
909+
throw new Error('error');
910+
},
911+
{message: 'my error'},
912+
),
913+
{
914+
assertion: 'throws',
915+
message: '',
916+
values: [
917+
{label: 'Function threw unexpected exception:', formatted: /error/},
918+
{label: 'Expected message to equal:', formatted: /my error/},
919+
],
920+
},
921+
);
922+
923+
passes(t, () => assertions.throws(() => {
924+
throw new Error('error');
925+
}, {message: 'error'}));
926+
927+
// Fails because the regular expression in the message is incorrect
928+
failsWith(
929+
t,
930+
() =>
931+
assertions.throws(
932+
() => {
933+
throw new Error('error');
934+
},
935+
{message: /my error/},
936+
),
937+
{
938+
assertion: 'throws',
939+
message: '',
940+
values: [
941+
{label: 'Function threw unexpected exception:', formatted: /error/},
942+
{label: 'Expected message to match:', formatted: /my error/},
943+
],
944+
},
945+
);
946+
947+
passes(t, () => assertions.throws(() => {
948+
throw new Error('error');
949+
}, {message: /error/}));
950+
951+
// Fails because the function in the message returns false
952+
failsWith(
953+
t,
954+
() =>
955+
assertions.throws(
956+
() => {
957+
throw new Error('error');
958+
},
959+
{message: () => false},
960+
),
961+
{
962+
assertion: 'throws',
963+
message: '',
964+
values: [
965+
{label: 'Function threw unexpected exception:', formatted: /error/},
966+
{label: 'Expected message to return true:', formatted: /Function/},
967+
],
968+
},
969+
);
970+
971+
passes(t, () => assertions.throws(() => {
972+
throw new Error('error');
973+
}, {message: () => true}));
902974
}));
903975

904976
test('.throws() returns the thrown error', t => {
@@ -1066,7 +1138,7 @@ test('.throws() fails if passed a bad expectation', t => {
10661138

10671139
failsWith(t, () => assertions.throws(() => {}, {message: null}), {
10681140
assertion: 'throws',
1069-
message: 'The `message` property of the second argument to `t.throws()` must be a string or regular expression',
1141+
message: 'The `message` property of the second argument to `t.throws()` must be a string, regular expression or a function',
10701142
values: [{label: 'Called with:', formatted: /message: null/}],
10711143
});
10721144

@@ -1136,7 +1208,7 @@ test('.throwsAsync() fails if passed a bad expectation', t => {
11361208

11371209
failsWith(t, () => assertions.throwsAsync(() => {}, {message: null}), {
11381210
assertion: 'throwsAsync',
1139-
message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string or regular expression',
1211+
message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string, regular expression or a function',
11401212
values: [{label: 'Called with:', formatted: /message: null/}],
11411213
}, {expectBoolean: false});
11421214

types/assertions.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type ThrowsExpectation = {
1212
is?: Error;
1313

1414
/** The thrown error must have a message that equals the given string, or matches the regular expression. */
15-
message?: string | RegExp;
15+
message?: string | RegExp | ((message: string) => boolean);
1616

1717
/** The thrown error must have a name that equals the given string. */
1818
name?: string;

0 commit comments

Comments
 (0)