Mocks vs stubs in testing
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Both replace a real dependency in a test. They differ in what you assert on.
A stub supplies canned answers so the code under test can run. You assert on the result.
A mock additionally records how it was called, so you can assert on the interaction — that something was called, how often, with what.
// stub — provide data, check the output
const getUser = vi.fn().mockResolvedValue({ id: 1, name: "Mai" });
expect(await greet(getUser)).toBe("Hello Mai");
// mock — check the call itself
const track = vi.fn();
checkout(cart, track);
expect(track).toHaveBeenCalledWith("purchase", { total: 100 });
Most libraries give you one object that can do both — Vitest and Jest both call it a mock function — so in practice the distinction is about which assertion you write.
The related terms
- Spy — wraps the real function, recording calls while still running it.
- Fake — a working lightweight implementation, such as an in-memory database.
- Dummy — a placeholder passed only to satisfy a signature.
When each is right
Use a stub when the dependency merely supplies input. Use a mock when the call is the behaviour you are testing — sending an email, recording analytics, writing to a queue. There is no return value to check there; the interaction is the point.
Where it goes wrong
Asserting on interactions couples the test to implementation details. Rewrite the function to fetch in a different order and the test fails even though the behaviour is identical — so prefer asserting on results wherever a result exists.
Heavy mocking is also a design signal: if a test needs six mocks, the unit has six dependencies. And mocking your own modules deeply means the suite verifies the mocks rather than the code. For HTTP, intercepting at the network layer with MSW usually beats mocking your fetch wrapper — the test then exercises the real code path.
