vitest-dev/vitest · error · Error

Cannot call "onTestFailed" inside a test hook.

Error message

Cannot call "onTestFailed" inside a test hook.

What it means

While executing setup/teardown hooks (beforeEach/afterEach/beforeAll/afterAll), Vitest replaces context.onTestFailed with a function that throws this error. onTestFailed only makes sense for the test about to run or that just ran; within a hook there is no single test to attach the failure handler to. The original context.onTestFailed is restored after hooks complete.

Solutions

  1. Move the onTestFailed registration into the test body where it belongs.
  2. If you need hook-scoped failure handling, capture failures via try/catch in the hook or use afterEach to inspect test.result.state.
  3. Make helpers accept a flag so they skip onTestFailed registration when called from hooks.

Example fix

// before
beforeEach(() => {
  ctx.onTestFailed(() => log('failed')) // throws
})

// after
test('x', () => {
  ctx.onTestFailed(() => log('failed'))
})
Defensive patterns

Strategy: validation

Validate before calling

function safeOnTestFailed(ctx, fn) {
  // only call from a test body; detect hook context heuristically
  if (!ctx.task?.result || ctx.task.result.state !== 'run') return
  ctx.onTestFailed(fn)
}

Prevention

When it happens

Trigger: Calling `context.onTestFailed(fn)` or `this.onTestFailed(fn)` from inside a beforeEach/afterEach/beforeAll/afterAll callback; calling it transitively via a helper invoked from a hook.

Common situations: Shared helpers that register onTestFailed unconditionally; refactoring a test-body assertion into a hook; library code (e.g., custom matchers) that registers failure callbacks.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/1e7573a3db72fcca. Report an issue: GitHub.

Appendix: source

Thrown at packages/vitest/src/runtime/runner/run.ts:139

  runner: VitestRunner,
  test: Test,
  hooks: ((context: TestContext) => Awaitable<void>)[],
  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))
      }

View on GitHub (pinned to 1fa9837ec2)