vitest-dev/vitest · error · Error

vi.when: no behavior defined when called with

Error message

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

What it means

Thrown at call-time by a mock configured with `vi.when(spy, { onUnmatched: 'throw' })` when the mock is invoked with arguments that match no registered `calledWith(...)` behavior. It is not raised for default `onUnmatched` (which falls back to the original implementation); it only fires when the caller explicitly opts into throwing on unmatched calls. The message echoes the received arguments.

Solutions

  1. Register a behavior for the actual argument set with another `.calledWith(...)` chain.
  2. If falling back is acceptable, remove `{ onUnmatched: 'throw' }` (or set `onUnmatched` to the original implementation) so unmatched calls invoke the real function.
  3. Use a custom `onUnmatched` function to log the call and return a sensible default for diagnosis.

Example fix

// before
const spy = vi.fn()
vi.when(spy, { onUnmatched: 'throw' }).calledWith(1).thenReturn('a')
spy(2) // throws

// after
const spy = vi.fn()
vi.when(spy, { onUnmatched: 'throw' })
  .calledWith(1).thenReturn('a')
  .calledWith(2).thenReturn('b')
spy(2) // 'b'
Defensive patterns

Strategy: fallback

Validate before calling

// before setting onUnmatched: 'throw', register a catch-all behavior
vi.when(spy).calledWith(expect.anything()).thenReturn('default')
vi.when(spy, { onUnmatched: 'throw' }).calledWith(1).thenReturn('a')

Try / catch

try {
  spy(unexpectedArg)
} catch (e) {
  if (e instanceof Error && /no behavior defined/.test(e.message)) {
    // register the missing behavior or relax onUnmatched
  }
  throw e
}

Prevention

When it happens

Trigger: Setting up `vi.when(spy, { onUnmatched: 'throw' }).calledWith(1).thenReturn('a')` and then calling `spy(2)` (or any arg set not registered). Also triggered by partial matches where equality testers (custom testers, `iterableEquality`) decide the args don't match a behavior.

Common situations: Strict-mocking every call site; a refactor that adds a new call path not yet stubbed; argument shape changes (e.g. extra property) that make previously-matching args unequal; flaky tests where call order varies.

Related errors


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

Appendix: 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 1fa9837ec2)