Skip to content

Commit 0621b2b

Browse files
ibuibuSimenB
andauthored
Update toThrow() to be able to use Error.cause (#13606)
Co-authored-by: Simen Bekkhus <[email protected]>
1 parent af5d5a1 commit 0621b2b

File tree

5 files changed

+103
-11
lines changed

5 files changed

+103
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[expect]` Update `toThrow()` to be able to use [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13606](https://github.com/facebook/jest/pull/13606))
56
- `[jest-core]` allow to use workerIdleMemoryLimit with only 1 worker or runInBand option ([#13846](https://github.com/facebook/jest/pull/13846))
67
- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13868](https://github.com/facebook/jest/pull/13868) & [#13912](https://github.com/facebook/jest/pull/13912))
78
- `[jest-runtime]` Revert `import assertions` for JSON modules as it's been relegated to Stage 2 ([#13911](https://github.com/facebook/jest/pull/13911))

packages/expect/src/__tests__/toThrowMatchers.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {alignedAnsiStyleSerializer} from '@jest/test-utils';
8+
import {alignedAnsiStyleSerializer, onNodeVersions} from '@jest/test-utils';
99
import jestExpect from '../';
1010

1111
expect.addSnapshotSerializer(alignedAnsiStyleSerializer);
@@ -278,6 +278,58 @@ describe.each(['toThrowError', 'toThrow'] as const)('%s', toThrow => {
278278
});
279279
});
280280

281+
describe('error message and cause', () => {
282+
const errorA = new Error('A');
283+
const errorB = new Error('B', {cause: errorA});
284+
const expected = new Error('good', {cause: errorB});
285+
286+
describe('pass', () => {
287+
test('isNot false', () => {
288+
jestExpect(() => {
289+
throw new Error('good', {cause: errorB});
290+
})[toThrow](expected);
291+
});
292+
293+
test('isNot true, incorrect message', () => {
294+
jestExpect(() => {
295+
throw new Error('bad', {cause: errorB});
296+
}).not[toThrow](expected);
297+
});
298+
299+
onNodeVersions('>=16.9.0', () => {
300+
test('isNot true, incorrect cause', () => {
301+
jestExpect(() => {
302+
throw new Error('good', {cause: errorA});
303+
}).not[toThrow](expected);
304+
});
305+
});
306+
});
307+
308+
describe('fail', () => {
309+
onNodeVersions('>=16.9.0', () => {
310+
test('isNot false, incorrect message', () => {
311+
expect(() =>
312+
jestExpect(() => {
313+
throw new Error('bad', {cause: errorB});
314+
})[toThrow](expected),
315+
).toThrow(
316+
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
317+
);
318+
});
319+
320+
test('isNot true, incorrect cause', () => {
321+
expect(() =>
322+
jestExpect(() => {
323+
throw new Error('good', {cause: errorA});
324+
})[toThrow](expected),
325+
).toThrow(
326+
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
327+
);
328+
});
329+
});
330+
});
331+
});
332+
281333
describe('asymmetric', () => {
282334
describe('any-Class', () => {
283335
describe('pass', () => {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"extends": "../../../../tsconfig.test.json",
3+
"compilerOptions": {
4+
"lib": ["es2022.error"]
5+
},
36
"include": ["./**/*"],
47
"references": [{"path": "../../"}, {"path": "../../../test-utils"}]
58
}

packages/expect/src/toThrowMatchers.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,23 @@ const toThrowExpectedObject = (
225225
thrown: Thrown | null,
226226
expected: Error,
227227
): SyncExpectationResult => {
228-
const pass = thrown !== null && thrown.message === expected.message;
228+
const expectedMessageAndCause = createMessageAndCause(expected);
229+
const thrownMessageAndCause =
230+
thrown !== null ? createMessageAndCause(thrown.value) : null;
231+
const pass =
232+
thrown !== null &&
233+
thrown.message === expected.message &&
234+
thrownMessageAndCause === expectedMessageAndCause;
229235

230236
const message = pass
231237
? () =>
232238
// eslint-disable-next-line prefer-template
233239
matcherHint(matcherName, undefined, undefined, options) +
234240
'\n\n' +
235-
formatExpected('Expected message: not ', expected.message) +
241+
formatExpected(
242+
`Expected ${messageAndCause(expected)}: not `,
243+
expectedMessageAndCause,
244+
) +
236245
(thrown !== null && thrown.hasMessage
237246
? formatStack(thrown)
238247
: formatReceived('Received value: ', thrown, 'value'))
@@ -242,22 +251,27 @@ const toThrowExpectedObject = (
242251
'\n\n' +
243252
(thrown === null
244253
? // eslint-disable-next-line prefer-template
245-
formatExpected('Expected message: ', expected.message) +
254+
formatExpected(
255+
`Expected ${messageAndCause(expected)}: `,
256+
expectedMessageAndCause,
257+
) +
246258
'\n' +
247259
DID_NOT_THROW
248260
: thrown.hasMessage
249261
? // eslint-disable-next-line prefer-template
250262
printDiffOrStringify(
251-
expected.message,
252-
thrown.message,
253-
'Expected message',
254-
'Received message',
263+
expectedMessageAndCause,
264+
thrownMessageAndCause,
265+
`Expected ${messageAndCause(expected)}`,
266+
`Received ${messageAndCause(thrown.value)}`,
255267
true,
256268
) +
257269
'\n' +
258270
formatStack(thrown)
259-
: formatExpected('Expected message: ', expected.message) +
260-
formatReceived('Received value: ', thrown, 'value'));
271+
: formatExpected(
272+
`Expected ${messageAndCause(expected)}: `,
273+
expectedMessageAndCause,
274+
) + formatReceived('Received value: ', thrown, 'value'));
261275

262276
return {message, pass};
263277
};
@@ -447,4 +461,26 @@ const formatStack = (thrown: Thrown | null) =>
447461
},
448462
);
449463

464+
function createMessageAndCauseMessage(error: Error): string {
465+
if (error.cause instanceof Error) {
466+
return `{ message: ${error.message}, cause: ${createMessageAndCauseMessage(
467+
error.cause,
468+
)}}`;
469+
}
470+
471+
return `{ message: ${error.message} }`;
472+
}
473+
474+
function createMessageAndCause(error: Error) {
475+
if (error.cause instanceof Error) {
476+
return createMessageAndCauseMessage(error);
477+
}
478+
479+
return error.message;
480+
}
481+
482+
function messageAndCause(error: Error) {
483+
return error.cause === undefined ? 'message' : 'message and cause';
484+
}
485+
450486
export default matchers;

packages/expect/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"extends": "../../tsconfig.json",
33
"compilerOptions": {
4-
"lib": ["es2020", "dom"],
4+
"lib": ["es2020", "es2021.promise", "es2022.error", "dom"],
55
"rootDir": "src",
66
"outDir": "build"
77
},

0 commit comments

Comments
 (0)