Skip to content
RungsySign in

Mocks, Stubs & Fakes

Isolating the thing under test without mocking so much you test nothing.

35 minDifficulty 3/5testingAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.

Before this

Why this exists

Testing a function that sends an email means either actually sending an email on every test run, or replacing the email sender with something fake that just records "you would have sent this." Mocking is that replacement — and overusing it produces a test suite that passes while testing almost nothing real.

The mental model

Three related but distinct tools, often lumped together as "mocking": a stub returns a canned value when called and nothing more; a fake is a lightweight working implementation, like an in-memory database standing in for a real one; a mock records how it was called so you can assert on the interaction itself, not just the return value.

How it works

Stub when you need a value, mock when you need to verify a call happened

If a test needs `getUser` to return a specific object so the rest of the function can run, a stub is right — you don't care whether it was called once or three times. If the whole point of the test is proving `sendEmail` was called with the right arguments, a mock is right — the call itself is the thing being verified.

Mocking your own code, not just external services, is a smell

Mocking a third-party payment API in a test is reasonable — you don't want to charge a real card. Mocking a function you wrote yourself, in the same codebase, usually means the test is checking implementation details rather than behaviour, and will break on any internal refactor even if the outcome is unchanged.

Over-mocking produces tests that pass against broken code

A test with five mocked dependencies is mostly testing the mocks' configured behaviour, not the real interaction between real pieces. If every dependency is fake, the test can pass even when the real dependencies would fail together — which is exactly the blind spot integration tests exist to cover.

Verify the mock was called correctly, not just that it was called

Asserting `sendEmail` was called once tells you almost nothing. Asserting it was called with the specific recipient, subject, and body you expected tells you the function actually did the right thing — the arguments passed to a mock are usually more informative than the call count.

The mechanism

A test replaces a real dependency with a mock before calling the function under test. The function calls the mock as if it were real, and the mock records the call — arguments, count, order. After the function runs, the test asserts both on the function's return value and, separately, on what the mock recorded about how it was used.

What people get wrong

Mocking everything makes tests more isolated and therefore better.
Isolation has a cost: a test with every dependency mocked can pass even when the real system would fail, because it's no longer testing any real interaction at all. The instinct toward maximum isolation is reasonable for unit tests of pure logic, but applied everywhere it produces a suite that gives false confidence about how the real system behaves.
Stubs, mocks, and fakes are all the same thing with different names.
They serve different purposes — a stub supplies a value, a fake provides working behaviour, a mock records and verifies interactions — and using the wrong one for the job produces a confusing or misleading test. Conflating them leads to tests that assert on call counts when they should be checking a return value, or vice versa, obscuring what the test is actually trying to prove.
If a test uses a mock, it can't be a good test.
Mocking a genuinely external, slow, or non-deterministic dependency — a third-party API, the current time, a random number generator — is often exactly right; the problem is specifically mocking your own internal collaborators. Overcorrecting into 'no mocks ever' produces tests that are slow, flaky, or impossible to write for anything touching an external system.

When not to use it

The dependency is a real, fast, deterministic piece of your own system, like an in-memory data structure.
Use the real thing directly rather than mocking it — mocking adds complexity for no benefit when the real dependency is already cheap and predictable.
You need confidence that your code and a real external service actually agree on behaviour.
A contract test or a real integration test against a sandbox environment of that service, not a mock — a mock can only assert against your own assumption of the service's behaviour, which is exactly what might be wrong.

Terms

Stub
A test double that returns a predetermined value when called, with no verification of how it was called.
Fake
A lightweight but genuinely working implementation of a dependency, such as an in-memory database used in place of a real one.
Mock
A test double that records how it was called so the test can assert on the interaction itself — arguments, call count, order.
Test double
The umbrella term for any stand-in — stub, fake, mock, or spy — used in place of a real dependency during a test.

Can you recall it?

What's the difference between a stub and a mock, and why does mocking your own internal functions (rather than external services) tend to produce brittle tests?

Keep track of this

Add Testing to your map and Rungsy will schedule reviews so you actually remember it.