← Back to Jest Mastery
Intermediate16 min read

Spies & Tracking

Spy on methods with jest.spyOn, track calls, and override implementations temporarily.

jest.spyOn Basics

jest.spyOn(object, "methodName") wraps real implementation by default while recording calls. Pass mock implementation as third argument to replace behavior temporarily.

Spies restore original with mockRestore()—call in afterEach to prevent leaking mocks across tests.

const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});

logError("fail");
expect(consoleSpy).toHaveBeenCalledWith("fail");
consoleSpy.mockRestore();

Call Tracking

Matchers include toHaveBeenCalled, toHaveBeenCalledTimes, toHaveBeenCalledWith, and toHaveBeenLastCalledWith. Access mock.calls and mock.results arrays for detailed inspection.

Use toHaveBeenNthCalledWith for ordered multi-call assertions in retry or batch scenarios.

const fn = jest.fn();
fn("a");
fn("b");

expect(fn).toHaveBeenCalledTimes(2);
expect(fn.mock.calls[0]).toEqual(["a"]);

Mock Return Values

mockReturnValueOnce chains sequential return values for repeated calls. mockResolvedValueOnce and mockRejectedValueOnce handle async sequences.

Default mockReturnValue applies to all calls after once chain exhausted unless reset.

  • mockClear resets call history but keeps implementation
  • mockReset clears history and removes implementation
  • Prefer restore over reset when done spying on real methods
const random = jest.spyOn(Math, "random")
  .mockReturnValueOnce(0.1)
  .mockReturnValueOnce(0.9);

Mock Implementations

mockImplementation receives call arguments and returns computed values—useful for simulating stateful dependencies. mockImplementationOnce applies per call.

Delegate to original with spy and partial override: jest.spyOn(obj, "m").mockImplementation((...args) => original(...args) + 1).

const spy = jest.spyOn(api, "getUser").mockImplementation(async (id) => ({
  id,
  name: "Test User",
}));

Spies vs Module Mocks

Spies work on existing object methods—great for console, timers, and imported namespace objects. Module mocks replace entire modules at import graph level.

Combine both carefully: spy on method of default export after jest.mock provides mock module object.

  • Avoid spying on production singletons without restore in afterEach
  • Track side effects via spies on collaborators, not internal private methods
  • Use jest.spyOn(Date, "now") with fake timers for time-dependent code

Get In Touch


Ready to discuss your next project? Drop me a message.