Skip to content

Commit a02e84b

Browse files
committed
fix(runner): make around run callbacks non-throwing
1 parent fa19b20 commit a02e84b

4 files changed

Lines changed: 112 additions & 6 deletions

File tree

docs/api/hooks.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ Register a callback function that wraps around each test within the current suit
158158

159159
The `runTest()` function runs `beforeEach` hooks, the test itself, fixtures accessed in the test, and `afterEach` hooks. Fixtures that are accessed in the `aroundEach` callback are initialized before `runTest()` is called and are torn down after the aroundEach teardown code completes, allowing you to safely use them in both setup and teardown phases.
160160

161+
`runTest()` does not throw when the wrapped test or inner hooks fail. Failures are still reported through Vitest results.
162+
161163
::: warning
162164
You **must** call `runTest()` within your callback. If `runTest()` is not called, the test will fail with an error.
163165
:::
@@ -268,6 +270,8 @@ Register a callback function that wraps around all tests within the current suit
268270

269271
The `runSuite()` function runs all tests in the suite, including `beforeAll`/`afterAll`/`beforeEach`/`afterEach` hooks, `aroundEach` hooks, and fixtures.
270272

273+
`runSuite()` does not throw when wrapped tests or inner hooks fail. Failures are still reported through Vitest results.
274+
271275
::: warning
272276
You **must** call `runSuite()` within your callback. If `runSuite()` is not called, the hook will fail with an error and all tests in the suite will be skipped.
273277
:::

packages/runner/src/run.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ interface AroundHooksOptions<THook extends Function> {
241241
hookName: 'aroundEach' | 'aroundAll'
242242
callbackName: 'runTest()' | 'runSuite()'
243243
onTimeout?: (error: Error) => void
244+
onInnerError?: (error: unknown) => void
244245
invokeHook: (hook: THook, use: () => Promise<void>) => Awaitable<unknown>
245246
}
246247

@@ -263,7 +264,7 @@ async function callAroundHooks<THook extends Function>(
263264
runInner: () => Promise<void>,
264265
options: AroundHooksOptions<THook>,
265266
): Promise<void> {
266-
const { hooks, hookName, callbackName, onTimeout, invokeHook } = options
267+
const { hooks, hookName, callbackName, onTimeout, onInnerError, invokeHook } = options
267268

268269
if (!hooks.length) {
269270
await runInner()
@@ -369,7 +370,7 @@ async function callAroundHooks<THook extends Function>(
369370
resolveUseReturned()
370371

371372
if (innerError) {
372-
throw innerError
373+
onInnerError?.(innerError)
373374
}
374375
}
375376

@@ -444,19 +445,22 @@ async function callAroundHooks<THook extends Function>(
444445

445446
async function callAroundAllHooks(
446447
suite: Suite,
448+
runner: VitestRunner,
447449
runSuiteInner: () => Promise<void>,
448450
): Promise<void> {
449451
await callAroundHooks(runSuiteInner, {
450452
hooks: getAroundAllHooks(suite),
451453
hookName: 'aroundAll',
452454
callbackName: 'runSuite()',
455+
onInnerError: error => failTask(suite.result!, error, runner.config.diffOptions),
453456
invokeHook: (hook, use) => hook(use, suite),
454457
})
455458
}
456459

457460
async function callAroundEachHooks(
458461
suite: Suite,
459462
test: Test,
463+
runner: VitestRunner,
460464
runTest: (fixtureCheckpoint: number) => Promise<void>,
461465
): Promise<void> {
462466
await callAroundHooks(
@@ -468,6 +472,7 @@ async function callAroundEachHooks(
468472
hooks: getAroundEachHooks(suite),
469473
hookName: 'aroundEach',
470474
callbackName: 'runTest()',
475+
onInnerError: error => failTask(test.result!, error, runner.config.diffOptions),
471476
onTimeout: error => abortContextSignal(test.context, error),
472477
invokeHook: (hook, use) => hook(use, test.context, suite),
473478
},
@@ -626,7 +631,7 @@ export async function runTest(test: Test, runner: VitestRunner): Promise<void> {
626631
// of fixture cleanup functions AFTER all aroundEach fixtures have been resolved
627632
// but BEFORE the test runs. This allows us to clean up only fixtures created
628633
// inside runTest while preserving aroundEach fixtures for teardown.
629-
await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => {
634+
await callAroundEachHooks(suite, test, runner, async (fixtureCheckpoint) => {
630635
try {
631636
await runner.onBeforeTryTask?.(test, {
632637
retry: retryCount,
@@ -861,7 +866,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner): Promise<void
861866
let suiteRan = false
862867

863868
try {
864-
await callAroundAllHooks(suite, async () => {
869+
await callAroundAllHooks(suite, runner, async () => {
865870
suiteRan = true
866871
try {
867872
// beforeAll

test/cli/test/around-each.test.ts

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,8 @@ test('multiple aroundEach hooks with different timeouts', async () => {
920920

921921
expect(extractLogs(stdout)).toMatchInlineSnapshot(`
922922
">> outer setup
923-
>> inner setup start"
923+
>> inner setup start
924+
>> outer teardown"
924925
`)
925926
expect(stderr).toMatchInlineSnapshot(`
926927
"
@@ -982,7 +983,8 @@ test('multiple aroundEach hooks where inner teardown times out', async () => {
982983
">> outer setup
983984
>> inner setup
984985
>> test
985-
>> inner teardown start"
986+
>> inner teardown start
987+
>> outer teardown"
986988
`)
987989
expect(stderr).toMatchInlineSnapshot(`
988990
"
@@ -1095,6 +1097,7 @@ test('aroundEach teardown timeout works when runTest error is caught', async ()
10951097
"caught-inner-error-timeout.test.ts": {
10961098
"suite": {
10971099
"test": [
1100+
"inner aroundEach teardown failure",
10981101
"The teardown phase of \"aroundEach\" hook timed out after 50ms.",
10991102
],
11001103
},
@@ -1103,6 +1106,51 @@ test('aroundEach teardown timeout works when runTest error is caught', async ()
11031106
`)
11041107
})
11051108

1109+
test('runTest does not throw when inner aroundEach fails', async () => {
1110+
const { errorTree } = await runInlineTests({
1111+
'non-throwing-run-test.test.ts': `
1112+
import { aroundEach, describe, expect, test } from 'vitest'
1113+
1114+
describe('suite', () => {
1115+
aroundEach(async (runTest) => {
1116+
let caught = false
1117+
try {
1118+
await runTest()
1119+
}
1120+
catch {
1121+
caught = true
1122+
}
1123+
1124+
if (caught) {
1125+
throw new Error('runTest should not throw')
1126+
}
1127+
})
1128+
1129+
aroundEach(async (runTest) => {
1130+
await runTest()
1131+
throw new Error('inner aroundEach teardown failure')
1132+
})
1133+
1134+
test('test', () => {
1135+
expect(1).toBe(1)
1136+
})
1137+
})
1138+
`,
1139+
})
1140+
1141+
expect(errorTree()).toMatchInlineSnapshot(`
1142+
{
1143+
"non-throwing-run-test.test.ts": {
1144+
"suite": {
1145+
"test": [
1146+
"inner aroundEach teardown failure",
1147+
],
1148+
},
1149+
},
1150+
}
1151+
`)
1152+
})
1153+
11061154
test('aroundEach with AsyncLocalStorage', async () => {
11071155
const { stdout, stderr, errorTree } = await runInlineTests({
11081156
'async-local-storage.test.ts': `
@@ -1705,6 +1753,7 @@ test('aroundAll teardown timeout works when runSuite error is caught', async ()
17051753
"caught-inner-suite-error-timeout.test.ts": {
17061754
"suite": {
17071755
"__suite_errors__": [
1756+
"inner aroundAll teardown failure",
17081757
"The teardown phase of \"aroundAll\" hook timed out after 50ms.",
17091758
],
17101759
"test": "passed",
@@ -1714,6 +1763,52 @@ test('aroundAll teardown timeout works when runSuite error is caught', async ()
17141763
`)
17151764
})
17161765

1766+
test('runSuite does not throw when inner aroundAll fails', async () => {
1767+
const { errorTree } = await runInlineTests({
1768+
'non-throwing-run-suite.test.ts': `
1769+
import { aroundAll, describe, expect, test } from 'vitest'
1770+
1771+
describe('suite', () => {
1772+
aroundAll(async (runSuite) => {
1773+
let caught = false
1774+
try {
1775+
await runSuite()
1776+
}
1777+
catch {
1778+
caught = true
1779+
}
1780+
1781+
if (caught) {
1782+
throw new Error('runSuite should not throw')
1783+
}
1784+
})
1785+
1786+
aroundAll(async (runSuite) => {
1787+
await runSuite()
1788+
throw new Error('inner aroundAll teardown failure')
1789+
})
1790+
1791+
test('test', () => {
1792+
expect(1).toBe(1)
1793+
})
1794+
})
1795+
`,
1796+
})
1797+
1798+
expect(errorTree()).toMatchInlineSnapshot(`
1799+
{
1800+
"non-throwing-run-suite.test.ts": {
1801+
"suite": {
1802+
"__suite_errors__": [
1803+
"inner aroundAll teardown failure",
1804+
],
1805+
"test": "passed",
1806+
},
1807+
},
1808+
}
1809+
`)
1810+
})
1811+
17171812
test('aroundAll with server start/stop pattern', async () => {
17181813
const { stdout, stderr, errorTree } = await runInlineTests({
17191814
'server.test.ts': `

test/cli/test/concurrent.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ test('aroundEach enforces teardown timeout when inner error is caught', async ()
956956
"basic.test.ts": {
957957
"wrapper": {
958958
"a": [
959+
"inner aroundEach teardown failure",
959960
"The teardown phase of \"aroundEach\" hook timed out after 50ms.",
960961
],
961962
},
@@ -1001,6 +1002,7 @@ test('aroundAll enforces teardown timeout when inner error is caught', async ()
10011002
"basic.test.ts": {
10021003
"suite": {
10031004
"__suite_errors__": [
1005+
"inner aroundAll teardown failure",
10041006
"The teardown phase of \"aroundAll\" hook timed out after 50ms.",
10051007
],
10061008
"a": "passed",

0 commit comments

Comments
 (0)