vitest-dev/vitest · error · Error

vi.when: no behavior defined when called with [${args.map(ar

Error message

vi.when: no behavior defined when called with [${args.map(arg => stringify(arg)).join(', ')}]

What it means

When a `vi.when` chain is created with `{ onUnmatched: 'throw' }`, calling the spy with arguments that match no registered `.calledWith(...)` behavior throws this error listing the unmatched arguments. This is an opt-in strict mode that surfaces unexpected calls during the test rather than silently returning undefined or falling through to the original implementation.

Source

Thrown at packages/vitest/src/integrations/mock/when.ts:330

      if (equals(args, behavior.arguments, testers)) {
        return behavior.actions.findLast(action => !(action.remaining === 0 && action.called)) ?? null
      }
    }

    return null
  }

  spy.mockImplementation(
    // @ts-expect-error cannot resolve generic args
    (...args: ScopedParameters) => {
      const action = findAction(args)

      if (action === null) {
        const onUnmatched = typeof options?.onUnmatched === 'function'
          ? options.onUnmatched
          : options?.onUnmatched === 'throw'
            ? () => {
                throw new Error(`vi.when: no behavior defined when called with [${args.map(arg => stringify(arg)).join(', ')}]`)
              }
            : originalImplementation
        return onUnmatched?.(...args)
      }

      action.remaining -= 1
      action.called = true

      switch (action.type) {
        case 'return': {
          return action.value
        }

        case 'throw': {
          throw action.value
        }

        case 'resolve': {

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Add a `vi.when(spy).calledWith(<unmatched args>).thenReturn(...)` for the call shown in the error message.
  2. Fix the call site to pass the arguments you actually intended to match.
  3. If unmatched calls are acceptable, drop `onUnmatched: 'throw'` (or set `'passthrough'`) to fall back to the original implementation.

Example fix

// before
vi.when(spy, { onUnmatched: 'throw' }).calledWith(1).thenReturn('a')
spy(2) // throws: no behavior defined when called with [2]
// after
vi.when(spy, { onUnmatched: 'throw' })
  .calledWith(1).thenReturn('a')
vi.when(spy).calledWith(2).thenReturn('b')
spy(2) // 'b'
Defensive patterns

Strategy: try-catch

Validate before calling

// only opt into onUnmatched:'throw' after registering all expected calls
const w = vi.when(spy) // default 'passthrough' first
w.calledWith(1).thenReturn('a')
// switch to strict only when coverage is complete, or assert via .toHaveBeenExhausted()

Try / catch

try {
  spy(unexpectedArg)
} catch (e) {
  if (e instanceof Error && e.message.startsWith('vi.when: no behavior defined')) {
    // register the missing behavior or fix the call site
  } else throw e
}

Prevention

When it happens

Trigger: Creating `vi.when(spy, { onUnmatched: 'throw' })`, registering behaviors for some argument sets, then invoking `spy(...)` with arguments that deep-equal none of the registered `calledWith` sets.

Common situations: Forgetting to register a behavior for a code path the test exercises, argument shape mismatches (e.g. passing a string where `calledWith` registered a number), or stale matchers after a refactor.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/0a2db21e1b5df8a8.json. Report an issue: GitHub.