skip to content

In the test-double taxonomy, how does a stub differ from a mock?

level: middleimportance: must knowfreq 68%

answer

  1. One enables, the other demands
  2. Ask who owns the verdict
  3. Declared before, or asserted after
  4. Value produced versus calls made
  5. The role belongs to the test

basics

~20 s

A stub supplies canned answers so the code under test can run; it can never fail a test. A mock is configured in advance with the calls it must receive and fails the test itself when the run does not match.

solid answer

~50 s

Both hand back values the test chose, so they look identical in construction — the difference is intent and who owns the verdict. A **stub** exists to *enable*: it feeds the code under test the input it needs to reach an interesting state, and the test's own assertions on the returned value or resulting state decide pass or fail. A **mock** exists to *demand*: the expected calls are declared before the exercise, and the double reports the mismatch, so the test can fail with no assertion written at the end. The two shade into the familiar pair of styles — checking the value the code produced, versus checking the calls the code made. Crucially the kind is a property of how a test *uses* the object, not of how it was built: the same stand-in is a stub on one method and a mock on another.

code

pseudocode · 9 lines
pseudocode
# same object, two roles, decided by the test
gateway = double(TrackingGateway)
gateway.status_of("PX-4417") returns "IN_TRANSIT"      # stubbed input
expect gateway.refresh("PX-4417") to be called once     # mocked expectation

tracker.report("PX-4417", gateway)

assert tracker.lastLabel == "In transit"                # state check
gateway.verifyExpectations()                            # behaviour check

go deeper

for a junior

Learn the one-line separator first: a stub feeds the code values, a mock insists on calls and can fail the run by itself. Being able to say which of the two a snippet is using is enough at this level.

for a middle

Explain that the kind is decided by how the test uses the object, not by how it was created, and show the same stand-in playing both roles. Interviewers expect you to connect the pair to value checking versus call checking.

for a senior

Justify the choice per test. Be ready to say that an expectation is right when the collaboration itself is the requirement, and wrong when it merely records today's call pattern, and to describe what the difference does to a suite as it ages.

for a principal

Own the review heuristic your team applies. You should be able to state a rule reviewers can apply without you in the room — when an expectation is warranted, and what a wall of them signals about the design underneath.

### Same shape, different job Build a stand-in for a parcel-tracking gateway, tell it to answer `status_of("PX-4417")` with `IN_TRANSIT`, and you cannot yet tell whether it is a stub or a mock. Nothing about the object decides that; the *test* decides it, by what it does next. If the test then asserts on the summary string the code under test returned, the gateway was a **stub**. Its whole job was to get the code to the interesting state — the assertion lives in the test, and the stub is inert scaffolding that could not fail anything if it tried. If instead the test declared beforehand that `refresh("PX-4417")` *must* be called exactly once, and the run ends by checking that declaration, the gateway was a **mock**. The expectation lives in the double. The test can end without a single explicit assertion and still fail, because the double is holding the verdict. ### Enabling versus demanding The cleanest way to hold the distinction is by intent: - A stub answers the question *"what does the world tell my code?"* It is an input. - A mock answers the question *"what does my code tell the world?"* It is an expected output. That maps onto the two verification styles people name in reviews: checking the **state** a run produced, versus checking the **behaviour** — the calls — a run performed. A stub belongs with state checking; a mock is the machinery of behaviour checking. ### Why the difference is worth arguing about It changes what the test is *about*, and therefore when it should break. A test built on stubs breaks when the code produces a wrong value. Rearrange the internals, change how many gateway calls happen, cache a response — the test stays green as long as the answer is right. That is usually what you want, because the answer is the requirement. A test built on mocks breaks when the calls change, whether or not the answer did. Sometimes that is exactly right: if the requirement literally *is* "the gateway is asked to refresh before we report a status", nothing about the return value can catch a regression, and only an expectation can. But if the calls are merely how today's implementation happens to work, the expectation freezes an accident into a requirement, and you have written a test that fails on refactors that broke nothing. So "stub or mock?" is really the question "is the collaboration itself the requirement, or just plumbing?" Candidates who answer with vocabulary alone miss that; candidates who answer with intent land it. ### The failure looks different too When a stub-based test fails, the message is about a value: expected `Delayed`, got `In transit`. Reading it tells you what the code got wrong. When a mock-based test fails, the message is about traffic: expected one call to `refresh`, saw none — or saw three. That is more diagnostic when the interaction matters and much less so when it does not, because the same message appears for a genuine bug and for a harmless restructuring. Teams that mock everything eventually learn to distrust the message, which is worse than not having the test. ### A worked case The rule under test: when the tracking gateway exceeds the team's 92nd-percentile budget of 340 ms, the caller reports the last cached status instead of waiting. Two tests, two different doubles: 1. *"An intermittent timeout falls back to cache."* The double throws a timeout on the call and the test asserts the returned status is the cached one. **Stub** — the timeout is an input, the returned status is the requirement. 2. *"A stale cache is refreshed before the next report."* The requirement is a call, and no return value reveals whether it happened. **Mock** — the expectation "refresh is invoked once for PX-4417" is the assertion, because there is nothing else to assert. Notice that the second test earns its mock by argument, not by habit. That is the standard an interviewer is listening for. ### The honest caveat The usage is not universal. Plenty of teams say "mock" for both, and tooling frequently offers one constructor for every kind, which trains people to use one word. In an interview, state the distinction and then say how you would phrase it on a real team: name the role and its behaviour together — "a stub, it returns a fixed status and the test asserts on the result" — so you are understood regardless of which vocabulary the room uses.

  • Can a test fail with no assertion statement in it at all?
    Yes, when a mock is involved. The expectation was declared before the exercise and the double checks it, so the failure originates in the double rather than in a line the author wrote at the end. That is often surprising to read, which is one reason some teams prefer recording the calls and asserting on them explicitly instead.
  • How do you decide whether a collaboration is a requirement or just plumbing?
    Ask whether a reviewer could describe it in the acceptance wording without mentioning code structure. "The gateway is refreshed before a status is reported" is a requirement someone outside the team can agree to. "The mapper is called twice" is implementation detail. If the collaboration would not appear in the requirement, an expectation on it is freezing an accident.
  • If a value can be asserted, is there ever still a reason to add an expectation on the same run?
    Sometimes, but it needs a reason. The usual one is that the value alone cannot distinguish two implementations — a cached and a freshly fetched answer are identical strings, so only the call, or the absence of one, tells them apart. Absent that, an expectation on top of a value assertion just adds a way to fail.

saying these in an interview costs you the question

  • Saying a stub can fail a test
  • Treating stub and mock as interchangeable words
  • Deciding the kind by how the object was constructed
  • Adding expectations to every collaborator by default
  • Claiming mocks always verify call order
  • Believing state checking is obsolete

context