Skip to content

Commit 464218f

Browse files
authored
fix: try to catch unhandled error outside of a test (#7968)
1 parent 5a91eca commit 464218f

File tree

7 files changed

+87
-9
lines changed

7 files changed

+87
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Next generation testing framework powered by Vite.
4949
- Out-of-box TypeScript / JSX support
5050
- Filtering, timeouts, concurrent for suite and tests
5151
- Sharding support
52+
- Reporting Uncaught Errors
5253
- Run your tests in the browser natively (experimental)
5354

5455
> Vitest requires Vite >=v5.0.0 and Node >=v18.0.0

docs/.vitepress/components/FeaturesList.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
<ListItem>Code coverage via <a target="_blank" href="https://v8.dev/blog/javascript-code-coverage" rel="noopener noreferrer">v8</a> or <a target="_blank" href="https://istanbul.js.org/" rel="noopener noreferrer">istanbul</a></ListItem>
2727
<ListItem>Rust-like <a href="/guide/in-source">in-source testing</a></ListItem>
2828
<ListItem>Type Testing via <a target="_blank" href="https://github.com/mmkal/expect-type" rel="noopener noreferrer">expect-type</a></ListItem>
29-
<ListItem>Sharding support</ListItem>
29+
<ListItem>Sharding Support</ListItem>
30+
<ListItem>Reporting Uncaught Errors</ListItem>
3031
</ul>
3132
</template>
3233

docs/guide/features.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,60 @@ export default defineConfig(({ mode }) => ({
259259
},
260260
}))
261261
```
262+
263+
## Unhandled Errors
264+
265+
By default, Vitest catches and reports all [unhandled rejections](https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event), [uncaught exceptions](https://nodejs.org/api/process.html#event-uncaughtexception) (in Node.js) and [error](https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event) events (in the [browser](/guide/browser/)).
266+
267+
You can disable this behaviour by catching them manually. Vitest assumes the callback is handled by you and won't report the error.
268+
269+
::: code-group
270+
```ts [setup.node.js]
271+
// in Node.js
272+
process.on('unhandledRejection', () => {
273+
// your own handler
274+
})
275+
276+
process.on('uncaughtException', () => {
277+
// your own handler
278+
})
279+
```
280+
```ts [setup.browser.js]
281+
// in the browser
282+
window.addEventListener('error', () => {
283+
// your own handler
284+
})
285+
286+
window.addEventListener('unhandledrejection', () => {
287+
// your own handler
288+
})
289+
```
290+
:::
291+
292+
Alternatively, you can also ignore reported errors with a [`dangerouslyIgnoreUnhandledErrors`](/config/#dangerouslyignoreunhandlederrors) option. Vitest will still report them, but they won't affect the test result (exit code won't be changed).
293+
294+
If you need to test that error was not caught, you can create a test that looks like this:
295+
296+
```ts
297+
test('my function throws uncaught error', async ({ onTestFinished }) => {
298+
onTestFinished(() => {
299+
// if the event was never called during the test,
300+
// make sure it's removed before the next test starts
301+
process.removeAllListeners('unhandledrejection')
302+
})
303+
304+
return new Promise((resolve, reject) => {
305+
process.once('unhandledrejection', (error) => {
306+
try {
307+
expect(error.message).toBe('my error')
308+
resolve()
309+
}
310+
catch (error) {
311+
reject(error)
312+
}
313+
})
314+
315+
callMyFunctionThatRejectsError()
316+
})
317+
})
318+
```

packages/vitest/src/runtime/execute.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,11 @@ function listenForErrors(state: () => WorkerGlobalState) {
5858
function catchError(err: unknown, type: string, event: 'uncaughtException' | 'unhandledRejection') {
5959
const worker = state()
6060

61-
// if error happens during a test
62-
if (worker.current?.type === 'test') {
63-
const listeners = process.listeners(event as 'uncaughtException')
64-
// if there is another listener, assume that it's handled by user code
65-
// one is Vitest's own listener
66-
if (listeners.length > 1) {
67-
return
68-
}
61+
const listeners = process.listeners(event as 'uncaughtException')
62+
// if there is another listener, assume that it's handled by user code
63+
// one is Vitest's own listener
64+
if (listeners.length > 1) {
65+
return
6966
}
7067

7168
const error = processError(err)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { expect, it } from 'vitest';
2+
3+
it('foo', () => {
4+
expect(1).toBe(1)
5+
new Promise((resolve, reject) => {
6+
reject('promise error');
7+
});
8+
});

test/cli/test/__snapshots__/fails.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,5 @@ exports[`should fail unhandled.test.ts 1`] = `
127127
"Error: some error
128128
Error: Uncaught [Error: some error]"
129129
`;
130+
131+
exports[`should fail unhandled-suite.test.ts 1`] = `"Unknown Error: promise error"`;

test/core/test/unhandled.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test } from 'vitest'
2+
3+
process.on('unhandledRejection', () => {
4+
// ignore errors
5+
})
6+
7+
test('throws unhandled but not reported', () => {
8+
// eslint-disable-next-line no-new
9+
new Promise((resolve, reject) => {
10+
reject(new Error('promise error'))
11+
})
12+
})

0 commit comments

Comments
 (0)