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

  1. Wrap the function first: `const spy = vi.fn(myFn); vi.when(spy).calledWith(...).thenReturn(...)`.
  2. For an object method, use `vi.spyOn(obj, 'method')` before passing it to `vi.when`.
  3. 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

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


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)