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

  1. Register onTestFinished inside the test body.
  2. Use afterEach for cleanup that must run after every test.
  3. 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

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


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)