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
- Wrap the function: `const fn = vi.fn(original)` then `expect(fn).toHaveBeenCalled()`.
- Use `vi.spyOn(object, 'method')` to spy on an existing method and assert on the returned spy.
- 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
- Create all collaborators with vi.fn / vi.spyOn in the same scope as the assertion.
- Pass the mock instance itself, never its results or return values.
- Re-wrap after mockRestore if you intend to keep asserting.
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
- is not a spy or a call to a spy
- expect.poll() is not supported in combination with .resolves
- expect.poll() is not supported in combination with .rejects
- expected function to throw an error, but it didn't
- .toHaveProperty() expects to receive a valid object, but got
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)