{"record":{"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":538,"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":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/expect/src/jest-expect.ts#L520-L556","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst fetch = (url) => Promise.resolve()\nexpect(fetch).toHaveBeenCalled()\n\n// after\nconst fetch = vi.fn(() => Promise.resolve())\nexpect(fetch).toHaveBeenCalled()","handlingStrategy":"type-guard","validationCode":"import { isMockFunction } from '@vitest/spy'\nif (!isMockFunction(fn)) { throw new Error('pass a vi.fn() spy') }\nexpect(fn).toHaveBeenCalled()","typeGuard":"const isSpy = (v: unknown): v is import('vitest').Mock =>\n  !!v && typeof v === 'function' && 'mock' in (v as any)","tryCatchPattern":null,"preventionTips":["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."],"tags":["expect","mock","spy","jest-expect"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}