jestjs/jest · error · Error

received value must be a mock function

Error message

received value must be a mock function

What it means

Thrown by `ensureMock` (spyMatchers.ts:1282), invoked from `toHaveReturned`, `toHaveReturnedTimes`, `toHaveReturnedWith`, `toHaveLastReturnedWith`, and `toHaveNthReturnedWith`. It fires when the received value is not a Jest mock function (`_isMockFunction === true`). These matchers specifically need `received.mock.results` (return values), which only Jest mocks carry — a Jasmine spy exposes `.calls` but not `.mock.results`, so `ensureMock` is stricter than `ensureMockOrSpy`.

Source

Thrown at packages/expect/src/spyMatchers.ts:1289

  if (!isMock(received) && !isSpy(received)) {
    throw new Error(
      matcherErrorMessage(
        matcherHint(matcherName, undefined, expectedArgument, options),
        `${RECEIVED_COLOR('received')} value must be a mock or spy function`,
        printWithType('Received', received, printReceived),
      ),
    );
  }
};

const ensureMock = (
  received: any,
  matcherName: string,
  expectedArgument: string,
  options: MatcherHintOptions,
) => {
  if (!isMock(received)) {
    throw new Error(
      matcherErrorMessage(
        matcherHint(matcherName, undefined, expectedArgument, options),
        `${RECEIVED_COLOR('received')} value must be a mock function`,
        printWithType('Received', received, printReceived),
      ),
    );
  }
};

export default spyMatchers;

View on GitHub (pinned to f49721c78e)

Solutions

  1. Use `jest.fn()` to create the mock so `.mock.results` is populated.
  2. If you only need call assertions (not return values), switch to `toHaveBeenCalled*` matchers which accept spies too.
  3. Log `received._isMockFunction` — must be `true` for `toHaveReturned*`.
  4. Ensure you are not passing a different framework's spy (sinon/vitest).

Example fix

// before
const fn = sinon.fake(); // not a Jest mock
run(fn);
expect(fn).toHaveReturnedWith('ok');

// after
const fn = jest.fn();
run(fn);
expect(fn).toHaveReturnedWith('ok');
Defensive patterns

Strategy: type-guard

Validate before calling

if (received == null || received._isMockFunction !== true) {
  throw new Error('received must be a jest.fn() mock (spies without .mock.results are not supported here)');
}
expect(received).toHaveReturned();

Type guard

const isJestMock = (v: unknown): v is {mock: {results: Array<unknown>; calls: Array<unknown>}; getMockName: () => string} =>
  v != null && (v as any)._isMockFunction === true;

Try / catch

try {
  expect(received).toHaveReturnedWith(value);
} catch (e) {
  if (e instanceof Error && /must be a mock function/.test(e.message)) {
    console.error('received is not a jest.fn() mock:', received);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `expect(plainFn).toHaveReturnedWith(value)` on a non-mock function; `expect(spy).toHaveReturned()` where `spy` is a Jasmine-style spy without a `.mock` property; `expect(obj.method).toHaveReturned()` on an unspied method; using a non-Jest fake (sinon stub, vitest spy) that doesn't expose `_isMockFunction`.

Common situations: Spying with a library other than Jest; forgetting `jest.fn()` around a callback; the function under test is the real implementation; using `toHaveReturned*` on something that was set up with `jest.spyOn` but where the return-tracking mock shape is missing (rare — `jest.spyOn` does produce a mock).

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/69d4e960a783ce2f.json. Report an issue: GitHub.