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
- Register a behavior for the actual argument set with another `.calledWith(...)` chain.
- If falling back is acceptable, remove `{ onUnmatched: 'throw' }` (or set `onUnmatched` to the original implementation) so unmatched calls invoke the real function.
- 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
- Register behaviors for every realistic call path before enabling onUnmatched: 'throw'.
- Use a custom onUnmatched function that logs unmatched calls during test development.
- When argument shapes change, update calledWith matchers alongside the production code.
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
- is not a `vi.when` instance
- vi.when: the argument must be a mock function created with…
- Cannot spy on export
- process.exit unexpectedly called with
- vi.when: `times` option must be greater than 0
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)