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

  1. Wrap the function first: `const spy = vi.fn(realFn)` or `const spy = vi.spyOn(obj, 'method')`, then `vi.when(spy)`.
  2. Ensure you pass the spy/mock variable itself, not the original implementation.
  3. 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

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


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/8eded8d9699f332e.json. Report an issue: GitHub.