vitest-dev/vitest · error · TypeError

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

Error message

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

What it means

TypeError thrown by the shared assertIsMock helper (used by every toHaveBeenCalled* matcher) when the assertion target is not a mock/spy. All spy matchers route through getSpy -> assertIsMock, which checks isMockFunction(assertion._obj) and refuses non-mocks before reading spy.mock.

Source

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

    }
    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 d568f8ce37)

Solutions

  1. Wrap the function with vi.fn(): const fn = vi.fn(); ...; expect(fn).toHaveBeenCalled().
  2. Use vi.spyOn(obj, 'method') and assert on the returned spy, not on obj.method directly.
  3. Ensure your vi.mock replacement returns vi.fn() instances, not plain functions.

Example fix

// before
const fetcher = (url) => realFetch(url);
expect(fetcher).toHaveBeenCalled();
// after
const fetcher = vi.fn((url) => realFetch(url));
expect(fetcher).toHaveBeenCalled();
Defensive patterns

Strategy: type-guard

Validate before calling

import { isMockFunction } from '@vitest/spy';
if (!isMockFunction(target)) throw new TypeError('target is not a mock — wrap with vi.fn()/vi.spyOn()');

Type guard

const isMock = (v: unknown): v is MockInstance => isMockFunction(v);

Prevention

When it happens

Trigger: expect(plainFunction).toHaveBeenCalled(); expect(42).toHaveBeenCalledWith('x'); expect({}).toHaveBeenCalledTimes(2). Any toHaveBeenCalled*/toBeCalled* matcher invoked on a value that is not a vi.fn()/vi.spyOn() instance.

Common situations: Forgetting to wrap a function with vi.fn() or vi.spyOn before asserting on it; asserting spy matchers on the original un-spied method; or passing a real implementation instead of the mock reference. Also happens when vi.mock factory returns a plain function rather than a mock.

Related errors


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