Jest · Unit tests that survive refactors

When do you use jest.fn versus jest.spyOn?

Mediumjest.fnjest.spyOnmockingUpdated Oct 2026
Quiz this track

Short answer

jest.fn creates a new mock with no original behaviour. jest.spyOn wraps an existing method, observes calls and can temporarily replace its implementation. Use a spy when the original object already exists.

The difference is what you start with. A mock function is a blank replacement you inject. A spy is attached to a real object or module method, so it preserves the relationship to that object and can call through to the original implementation.

ts
const spy = jest.spyOn(storage, "getItem");

storage.getItem("token");

expect(spy).toHaveBeenCalledWith("token");
spy.mockRestore();

const callback = jest.fn().mockReturnValue(42);
expect(handler(callback)).toBe(42);
The spy observes an existing method; the mock function is injected where a dependency is expected.

Choosing between them

  • Use jest.fn for a callback, constructor argument or injected dependency
  • Use jest.spyOn to observe a real method without replacing it
  • Add .mockImplementation to a spy when you need to override the original
  • Call spy.mockRestore() or jest.restoreAllMocks() to undo the wrap

Spying on a module export is not always possible with native ES modules because the binding is read-only from the importing side. In that case, mock the module with jest.mock and a factory instead of trying to patch the export. With CommonJS, spies on the module object usually work.

A spy that is never restored leaks into later tests in the same file. Set restoreMocks: true in the Jest config or restore in afterEach so the next test sees the real method.

Follow-ups you should expect

Interviewers use these to check whether the first answer was understanding or memorisation.

  1. 1Why can spying on an ESM export fail?
  2. 2What does mockRestore do to a spy?
  3. 3Can a spy call through to the original implementation?
  4. 4How do you configure automatic mock restoration?

Say it in the interview

jest.fn creates a fresh mock, so I use it when a dependency is injected and there is no real function to observe. jest.spyOn wraps an existing method, which is better when I want to verify it was called or temporarily change it. A spy can call through to the original unless I override it. I always restore spies or enable restoreMocks, otherwise the change leaks into later tests.

Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.

Related questions

Reading is step one

Now say it out loud.

Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Jest mock interview and get scored feedback on depth, clarity and confidence.