{"id":"8eded8d9699f332e","repo":"vitest-dev/vitest","slug":"vi-when-the-argument-must-be-a-mock-function-crea","errorCode":null,"errorMessage":"vi.when: the argument must be a mock function created with `vi.fn()` or `vi.spyOn()`","messagePattern":"vi\\.when: the argument must be a mock function created with `vi\\.fn\\(\\)` or `vi\\.spyOn\\(\\)`","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/integrations/mock/when.ts","lineNumber":296,"sourceCode":" *\n *   expect(spy('darkMode')).toBe(true)\n * }\n *\n * // spy's original implementation is restored here\n * expect(spy('darkMode')).toBe(undefined)\n *\n * @example\n * // Throw on unmatched calls\n * vi.when(spy, { onUnmatched: 'throw' })\n *   .calledWith(1)\n *   .thenReturn({ id: 1, name: 'Alice' })\n *\n * expect(spy(1)).toEqual({ id: 1, name: 'Alice' })\n * expect(() => spy(2)).toThrow()\n */\nexport function when<Fn extends Procedure>(spy: Fn | Mock<Fn>, options?: WhenOptions<Fn>): When<Fn> {\n  if (!isMockFunction(spy)) {\n    throw new TypeError('vi.when: the argument must be a mock function created with `vi.fn()` or `vi.spyOn()`')\n  }\n\n  type ScopedParameters = Parameters<Fn>\n  type ScopedReturn = ReturnType<Fn>\n\n  const behaviors: Behavior<ScopedParameters, ScopedReturn>[] = []\n  const originalImplementation = spy.getMockImplementation()\n\n  function findAction(args: ScopedParameters) {\n    const testers = [\n      ...getCustomEqualityTesters(),\n      iterableEquality,\n    ]\n\n    for (const behavior of behaviors) {\n      if (equals(args, behavior.arguments, testers)) {\n        return behavior.actions.findLast(action => !(action.remaining === 0 && action.called)) ?? null\n      }","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/integrations/mock/when.ts#L278-L314","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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()`."],"exampleFix":"// before\nimport { getUser } from './api'\nvi.when(getUser).calledWith(1).thenReturn({ id: 1 })\n// after\nimport * as api from './api'\nconst spy = vi.spyOn(api, 'getUser')\nvi.when(spy).calledWith(1).thenReturn({ id: 1 })","handlingStrategy":"type-guard","validationCode":"import { isMockFunction, vi } from 'vitest'\nfunction toWhen<T extends (...a: any[]) => any>(fn: T) {\n  if (!isMockFunction(fn)) throw new TypeError('pass a vi.fn()/vi.spyOn() result')\n  return vi.when(fn)\n}","typeGuard":"import { isMockFunction } from 'vitest'\n// isMockFunction(fn): fn is Mock — checks `_isMockFunction === true`","tryCatchPattern":null,"preventionTips":["Always create the mock with `vi.fn()` or `vi.spyOn()` before `vi.when`.","Pass the spy variable, never the original unwrapped function."],"tags":["mock","vi-when","spy","type-error"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}