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
- Add a `vi.when(spy).calledWith(<unmatched args>).thenReturn(...)` for the call shown in the error message.
- Fix the call site to pass the arguments you actually intended to match.
- 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
- Register behaviors for every call path the test exercises before using `onUnmatched: 'throw'`.
- Use `.toHaveBeenExhausted()` to verify all behaviors were consumed instead of relying solely on strict mode.
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
- ${utils.inspect(chain)} is not a `vi.when` instance
- vi.when: the argument must be a mock function created with `
- vi.when: `times` option must be greater than 0
- .toContainHTML() expects a string value, got ${htmlText}
- Exact option does not support RegExp expected class names
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/0a2db21e1b5df8a8.json.
Report an issue: GitHub.