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
Thrown by `vi.when(...)` when the `spy` argument fails `isMockFunction(...)`. `vi.when` stubs behavior on a mock by calling `spy.mockImplementation`, so the input must be a Vitest mock created via `vi.fn()` or `vi.spyOn()`. A TypeError is raised because passing any other value is a programming error, not a runtime condition.
Solutions
- Wrap the function first: `const spy = vi.fn(myFn); vi.when(spy).calledWith(...).thenReturn(...)`.
- For an object method, use `vi.spyOn(obj, 'method')` before passing it to `vi.when`.
- Double-check the import returns the mock you expect (e.g. after `vi.mock(...)` with a factory).
Example fix
// before
function add(a, b) { return a + b }
vi.when(add).calledWith(1, 2).thenReturn(3)
// after
const add = vi.fn((a, b) => a + b)
vi.when(add).calledWith(1, 2).thenReturn(3) Defensive patterns
Strategy: type-guard
Validate before calling
import { isMockFunction } from '@vitest/spy'
function whenSafe(spy) {
if (!isMockFunction(spy)) throw new TypeError('expected a vi.fn/vi.spyOn mock')
return vi.when(spy)
} Type guard
import { isMockFunction } from '@vitest/spy'
function isMockable(v): v is ReturnType<typeof vi.fn> {
return isMockFunction(v)
} Prevention
- Always create the mock with vi.fn() or vi.spyOn() before passing to vi.when.
- Add a type guard helper in test utilities to catch non-mocks early.
- Verify module mocks return the mocked implementation, not the original.
When it happens
Trigger: Calling `vi.when(realFn)` with a plain function; `vi.when(obj.method)` where the method was never spied on; passing a jest.fn() (non-Vitest mock) across an interop boundary; passing `undefined` because the import resolved to undefined.
Common situations: Forgetting to wrap a function with `vi.fn()`; spying on a method that doesn't exist on the target; module mock returning the un-mocked original; refactoring that loses the mock reference.
Related errors
- is not a `vi.when` instance
- vi.when: no behavior defined when called with
- aria adapter expects an Element
- Cannot spy on export
- Expecting a valid DOM element, but got
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/8eded8d9699f332e.
Report an issue: GitHub.
Appendix: 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 1fa9837ec2)