vitest-dev/vitest · error · TypeError

is not a `vi.when` instance

Error message

${utils.inspect(chain)} is not a `vi.when` instance

What it means

Thrown by the `toHaveBeenExhausted` Chai assertion when the asserted value is not a chain returned by `vi.when(...)`. The assertion inspects the object via `isWhenChain` (which checks for the internal `$$vitest:when` symbol); if the symbol is absent it raises a TypeError. It exists to stop developers from calling the assertion on a plain mock, spy, or unrelated value, since exhaustion tracking is only meaningful for `vi.when` behaviors.

Solutions

  1. Capture the return of `vi.when(spy)` and assert against that: `const w = vi.when(spy); ...; expect(w).toHaveBeenExhausted()`.
  2. If you intended to check call counts instead, use `expect(spy).toHaveBeenCalledTimes(n)`.
  3. Remove the `toHaveBeenExhausted` assertion if no `vi.when` behavior was set up.

Example fix

// before
const spy = vi.fn()
vi.when(spy).calledWith(1).thenReturn('a')
expect(spy).toHaveBeenExhausted()

// after
const spy = vi.fn()
const w = vi.when(spy).calledWith(1).thenReturn('a')
expect(w).toHaveBeenExhausted()
Defensive patterns

Strategy: type-guard

Validate before calling

import { isWhenChain } from 'vitest/vi' // or from the mock internals
if (!isWhenChain(target)) {
  throw new Error('Pass the vi.when(...) return value, not the mock itself')
}
expect(target).toHaveBeenExhausted()

Type guard

import { isWhenChain } from '@vitest/spy'
function assertWhenChain(v: unknown) {
  if (!isWhenChain(v as object)) throw new TypeError('not a vi.when instance')
  return v
}

Try / catch

try {
  expect(target).toHaveBeenExhausted()
} catch (e) {
  if (e instanceof TypeError && /vi\.when/.test(e.message)) {
    // re-route: capture the vi.when return value before asserting
  }
  throw e
}

Prevention

When it happens

Trigger: Calling `expect(spy).toHaveBeenExhausted()` where `spy` is a `vi.fn()`/`vi.spyOn()` result instead of the object returned by `vi.when(spy)`; calling it on a mock that was never passed through `vi.when`; passing a non-mock value like a plain object or number.

Common situations: Confusing the mock function with the `vi.when` return value; refactoring code so the `when` result is no longer in scope; copy-pasting an assertion without setting up `vi.when`.

Related errors


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

Appendix: source

Thrown at packages/vitest/src/integrations/mock/chai.ts:13

import type { ChaiPlugin } from '@vitest/expect'
import { wrapAssertion } from '@vitest/expect'
import { isWhenChain } from './when'

export const MockPlugin: ChaiPlugin = (chai, utils) => {
  utils.addMethod(
    chai.Assertion.prototype,
    'toHaveBeenExhausted',
    wrapAssertion(utils, 'toHaveBeenExhausted', function (this) {
      const chain = utils.flag(this, 'object')

      if (!isWhenChain(chain)) {
        throw new TypeError(
          `${utils.inspect(chain)} is not a \`vi.when\` instance`,
        )
      }

      const diagnostics = chain._getDiagnostics()

      this.assert(
        diagnostics.isExhausted,
        `expected all behaviors to have been exhausted, but some remain:\n\n  ${diagnostics.pendingBehaviors.replaceAll(/\n(?!\n)/g, '\n  ')}`,
        'expected at least one behavior to remain un-exhausted, but all were',
      )
    }),
  )
}

View on GitHub (pinned to 1fa9837ec2)