{"id":"4feafa5124bbd6ba","repo":"vitest-dev/vitest","slug":"utils-inspect-assertion-obj-is-not-a-spy-or-a","errorCode":null,"errorMessage":"${utils.inspect(assertion._obj)} is not a spy or a call to a spy!","messagePattern":"(.+?) is not a spy or a call to a spy!","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/expect/src/jest-expect.ts","lineNumber":533,"sourceCode":"    }\n    else {\n      expectedDiff = 10 ** -precision / 2\n      receivedDiff = Math.abs(expected - received)\n      pass = receivedDiff < expectedDiff\n    }\n    return this.assert(\n      pass,\n      `expected #{this} to be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`,\n      `expected #{this} to not be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`,\n      received,\n      expected,\n      false,\n    )\n  })\n\n  function assertIsMock(assertion: any) {\n    if (!isMockFunction(assertion._obj)) {\n      throw new TypeError(\n        `${utils.inspect(assertion._obj)} is not a spy or a call to a spy!`,\n      )\n    }\n  }\n\n  function getSpy(assertion: any) {\n    assertIsMock(assertion)\n    return assertion._obj as MockInstance\n  }\n\n  def(['toHaveBeenCalledTimes', 'toBeCalledTimes'], function (number: number) {\n    const spy = getSpy(this)\n    const spyName = spy.getMockName()\n    const callCount = spy.mock.calls.length\n    return this.assert(\n      callCount === number,\n      `expected \"${spyName}\" to be called #{exp} times, but got ${callCount} times`,\n      `expected \"${spyName}\" to not be called #{exp} times`,","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/expect/src/jest-expect.ts#L515-L551","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap the function with vi.fn(): const fn = vi.fn(); ...; expect(fn).toHaveBeenCalled().","Use vi.spyOn(obj, 'method') and assert on the returned spy, not on obj.method directly.","Ensure your vi.mock replacement returns vi.fn() instances, not plain functions."],"exampleFix":"// before\nconst fetcher = (url) => realFetch(url);\nexpect(fetcher).toHaveBeenCalled();\n// after\nconst fetcher = vi.fn((url) => realFetch(url));\nexpect(fetcher).toHaveBeenCalled();","handlingStrategy":"type-guard","validationCode":"import { isMockFunction } from '@vitest/spy';\nif (!isMockFunction(target)) throw new TypeError('target is not a mock — wrap with vi.fn()/vi.spyOn()');","typeGuard":"const isMock = (v: unknown): v is MockInstance => isMockFunction(v);","tryCatchPattern":null,"preventionTips":["Always wrap functions with vi.fn before asserting on calls.","Share the spy variable across arrange/act/assert to avoid referencing the original."],"tags":["typeerror","mock","spy","tohavebeencalled","type-mismatch"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}