vitest-dev/vitest · error · TypeError

is not a spy or a call to a spy!

Error message

${utils.inspect(assertion._obj)} is not a spy or a call to a spy!

What it means

`assertIsMock` runs at the top of every spy-related matcher (`toHaveBeenCalledTimes`, `toHaveBeenCalledWith`, etc.) and rejects the call if `expect()` was not given a mock/spy. It is a guard so the matcher can safely call `.mock` internals. Raised as a `TypeError` with an inspected view of the wrong value.

Solutions

  1. Wrap the function: `const fn = vi.fn(original)` then `expect(fn).toHaveBeenCalled()`.
  2. Use `vi.spyOn(object, 'method')` to spy on an existing method and assert on the returned spy.
  3. Verify you are passing the mock itself, not `mock.results`, `mock.mock`, or a call return value.

Example fix

// before
const fetch = (url) => Promise.resolve()
expect(fetch).toHaveBeenCalled()

// after
const fetch = vi.fn(() => Promise.resolve())
expect(fetch).toHaveBeenCalled()
Defensive patterns

Strategy: type-guard

Validate before calling

import { isMockFunction } from '@vitest/spy'
if (!isMockFunction(fn)) { throw new Error('pass a vi.fn() spy') }
expect(fn).toHaveBeenCalled()

Type guard

const isSpy = (v: unknown): v is import('vitest').Mock =>
  !!v && typeof v === 'function' && 'mock' in (v as any)

Prevention

When it happens

Trigger: Calling `expect(fn).toHaveBeenCalledTimes(1)` where `fn` is a plain function that was never wrapped with `vi.fn()` / `vi.spyOn()`; passing a stale reference after `mockRestore()` reset the binding; calling spy matchers on a value that is a mock result rather than the mock itself.

Common situations: Forgetting to wrap a collaborator with `vi.fn()`; spying on a method but asserting on the original function; passing `mock.results` or a return value instead of the mock instance; using `jest.fn()` semantics without creating the mock.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/4feafa5124bbd6ba. Report an issue: GitHub.

Appendix: source

Thrown at packages/expect/src/jest-expect.ts:538

    }
    else {
      expectedDiff = 10 ** -precision / 2
      receivedDiff = Math.abs(expected - received)
      pass = receivedDiff < expectedDiff
    }
    return this.assert(
      pass,
      `expected #{this} to be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`,
      `expected #{this} to not be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`,
      received,
      expected,
      false,
    )
  })

  function assertIsMock(assertion: any) {
    if (!isMockFunction(assertion._obj)) {
      throw new TypeError(
        `${utils.inspect(assertion._obj)} is not a spy or a call to a spy!`,
      )
    }
  }

  function getSpy(assertion: any) {
    assertIsMock(assertion)
    return assertion._obj as MockInstance
  }

  def(['toHaveBeenCalledTimes', 'toBeCalledTimes'], function (number: number) {
    const spy = getSpy(this)
    const spyName = spy.getMockName()
    const callCount = spy.mock.calls.length
    return this.assert(
      callCount === number,
      `expected "${spyName}" to be called #{exp} times, but got ${callCount} times`,
      `expected "${spyName}" to not be called #{exp} times`,

View on GitHub (pinned to 1fa9837ec2)