vitest-dev/vitest · error · Error
Cannot call "onTestFinished" inside a test hook.
Error message
Cannot call "onTestFinished" inside a test hook.
What it means
Twin of the onTestFailed guard: while hooks run, context.onTestFinished is replaced with a throwing stub. onTestFinished registers a callback for the current test's completion, which is meaningless inside a hook that runs outside the per-test execution frame. The real onTestFinished is restored after the hooks finish.
Solutions
- Register onTestFinished inside the test body.
- Use afterEach for cleanup that must run after every test.
- For per-test dynamic cleanup, pass the test context into your helper and call onTestFinished from the test.
Example fix
// before
beforeEach(() => {
ctx.onTestFinished(() => stopServer()) // throws
})
// after
test('x', () => {
ctx.onTestFinished(() => stopServer())
}) Defensive patterns
Strategy: validation
Validate before calling
function safeOnTestFinished(ctx, fn) {
if (!ctx.task?.result || ctx.task.result.state !== 'run') return
ctx.onTestFinished(fn)
} Prevention
- Register onTestFinished inside the test body, not in hooks.
- Use afterEach for deterministic post-test cleanup.
- Pass test context explicitly to shared helpers.
When it happens
Trigger: Calling `context.onTestFinished(fn)` from inside any beforeAll/afterAll/beforeEach/afterEach hook body, directly or through a helper.
Common situations: Resource-cleanup helpers that try to register onTestFinished regardless of call site; converting a test-body cleanup into a shared hook without removing the onTestFinished call.
Related errors
- Cannot call "onTestFailed" inside a test hook.
- Hook () can only be called inside a test
- Cannot annotate tests outside of the test run. The test
- expect.soft() can only be used inside a test
- The ` ` callback was called multiple times in the ` ` hook…
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/e697a1e872c975a5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/vitest/src/runtime/runner/run.ts:142
sequence: SequenceHooks,
) {
if (sequence === 'stack') {
hooks = hooks.slice().reverse()
}
if (!hooks.length) {
return
}
const context = test.context as WriteableTestContext
const onTestFailed = test.context.onTestFailed
const onTestFinished = test.context.onTestFinished
context.onTestFailed = () => {
throw new Error(`Cannot call "onTestFailed" inside a test hook.`)
}
context.onTestFinished = () => {
throw new Error(`Cannot call "onTestFinished" inside a test hook.`)
}
if (sequence === 'parallel') {
try {
await Promise.all(hooks.map(fn => limitMaxConcurrency(() => fn(test.context))))
}
catch (e) {
failTask(test.result!, e, runner.config._diffOptions)
}
}
else {
for (const fn of hooks) {
try {
await limitMaxConcurrency(() => fn(test.context))
}
catch (e) {
failTask(test.result!, e, runner.config._diffOptions)
}View on GitHub (pinned to 1fa9837ec2)