vitest-dev/vitest · error · TypeError
vi.when: the argument must be a mock function created with `
Error message
vi.when: the argument must be a mock function created with `vi.fn()` or `vi.spyOn()`
What it means
`vi.when(spy)` attaches conditional behaviors to a mock; it begins by calling `isMockFunction(spy)` which checks for the `_isMockFunction === true` marker. A plain function, arrow function, class method, or any non-mock value lacks the mock bookkeeping (`.mockImplementation`, call tracking) that `vi.when` relies on, so it throws a `TypeError` immediately.
Source
Thrown at packages/vitest/src/integrations/mock/when.ts:296
*
* expect(spy('darkMode')).toBe(true)
* }
*
* // spy's original implementation is restored here
* expect(spy('darkMode')).toBe(undefined)
*
* @example
* // Throw on unmatched calls
* vi.when(spy, { onUnmatched: 'throw' })
* .calledWith(1)
* .thenReturn({ id: 1, name: 'Alice' })
*
* expect(spy(1)).toEqual({ id: 1, name: 'Alice' })
* expect(() => spy(2)).toThrow()
*/
export function when<Fn extends Procedure>(spy: Fn | Mock<Fn>, options?: WhenOptions<Fn>): When<Fn> {
if (!isMockFunction(spy)) {
throw new TypeError('vi.when: the argument must be a mock function created with `vi.fn()` or `vi.spyOn()`')
}
type ScopedParameters = Parameters<Fn>
type ScopedReturn = ReturnType<Fn>
const behaviors: Behavior<ScopedParameters, ScopedReturn>[] = []
const originalImplementation = spy.getMockImplementation()
function findAction(args: ScopedParameters) {
const testers = [
...getCustomEqualityTesters(),
iterableEquality,
]
for (const behavior of behaviors) {
if (equals(args, behavior.arguments, testers)) {
return behavior.actions.findLast(action => !(action.remaining === 0 && action.called)) ?? null
}View on GitHub (pinned to d568f8ce37)
Solutions
- Wrap the function first: `const spy = vi.fn(realFn)` or `const spy = vi.spyOn(obj, 'method')`, then `vi.when(spy)`.
- Ensure you pass the spy/mock variable itself, not the original implementation.
- If importing a mock from another framework, re-create it with `vi.fn()`.
Example fix
// before
import { getUser } from './api'
vi.when(getUser).calledWith(1).thenReturn({ id: 1 })
// after
import * as api from './api'
const spy = vi.spyOn(api, 'getUser')
vi.when(spy).calledWith(1).thenReturn({ id: 1 }) Defensive patterns
Strategy: type-guard
Validate before calling
import { isMockFunction, vi } from 'vitest'
function toWhen<T extends (...a: any[]) => any>(fn: T) {
if (!isMockFunction(fn)) throw new TypeError('pass a vi.fn()/vi.spyOn() result')
return vi.when(fn)
} Type guard
import { isMockFunction } from 'vitest'
// isMockFunction(fn): fn is Mock — checks `_isMockFunction === true` Prevention
- Always create the mock with `vi.fn()` or `vi.spyOn()` before `vi.when`.
- Pass the spy variable, never the original unwrapped function.
When it happens
Trigger: Passing anything other than a `vi.fn()` or `vi.spyOn()` result to `vi.when(...)`: a real imported function, a bound method, `jest.fn()`-style mock from another lib without the marker, or a non-function value.
Common situations: Spying on the wrong target (e.g. the class instead of an instance method), passing the unwrapped export, or assuming `vi.when` works on any callable.
Related errors
- ${utils.inspect(chain)} is not a `vi.when` instance
- ${utils.inspect(assertion._obj)} is not a spy or a call to a
- ${msg}
- ${formatCalls(spy, msg, args)}
- ${utils.inspect(resultSpy)} is not a spy or a call to a spy
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/8eded8d9699f332e.json.
Report an issue: GitHub.