The bug-fix habit, restated
Lesson 4-3's rule says the first thing to write is a failing test that reproduces the bug, before touching the code.
Reproduce first, as a failing test. When the fix flips it from red to green you have proof, and the suite keeps that bug from ever returning silently.
That habit carries directly into this unit. Reproducing a bug sometimes requires pinning down a dependency that will not sit still, such as a clock, a network call, or a random number, and test doubles are the tool for that.
The unit and its noisy neighbors
A unit test checks one small piece of code, the unit, in isolation. But real functions have dependencies: they call the network, read the clock, query a database, or roll random numbers. Those neighbors poison tests in three ways:
- slow: a real API call takes seconds, and you want thousands of tests per minute
- flaky: the network can fail even when your logic is perfect, so the test lies
- nondeterministic: the clock and random values change every run, so there is no fixed expected value to assert
The cure is a test double: a stand-in object or function that plays the dependency's role during the test, like a stunt double in a film. Your logic runs for real, the neighbors are actors.
The three doubles you will actually meet
People use mock loosely for all of these, and the distinctions matter:
- a stub returns canned answers. It exists so the unit has data to work with, for example a
fetch_tempthat always says 35 - a fake is a real but lightweight implementation, for example an in-memory dictionary standing in for a database
- a mock records how it was called, so the test can assert on the interaction afterward, checking that
send_emailwas called exactly once with a particular address
The choice follows from what the test is trying to claim:
| Double | Use it when you care about | The assert looks at |
|---|---|---|
| stub | what the unit returns | the return value |
| mock | what the unit does to the outside world | the recorded calls |
| fake | the unit needing a dependency that genuinely works | the result, via the fake's state |
The next three lessons build one of each by hand, in that order. Building them yourself matters, because the mocking libraries you will meet on the job are conveniences over exactly these ideas, and the libraries are much harder to use well if the underlying shape is unfamiliar.
Choosing a double for a payment call
Testing charge_customer, where the important claim is that it calls the payment gateway exactly once with the right amount, calls for a mock.
The behavior under test is the interaction itself, meaning one call with the correct amount, and recording calls so a test can assert on them is precisely what a mock is for.
A stub is the wrong fit here, since it can hand back a success response but cannot tell you whether the gateway was called twice. A double charge is the exact bug this test needs to catch, and a stub would let it through.
Using the real gateway is worse than wrong, since it moves real money. A payment API is the clearest example of a dependency you must never touch from a test suite, alongside anything that sends email to real addresses or deletes production rows.
The real cost of a flaky test
A test that calls the real network and fails once a week despite correct code does damage well beyond the wasted reruns. The deepest cost is that red stops meaning the code is broken, so real failures get ignored along with the false alarms.
The safety net from unit 1 only works while a red run reliably means a real problem. One flaky test teaches the whole team to shrug at red, and that habit swallows genuine regressions too, because nobody investigates a color they have learned to distrust.
The damage compounds in a way that is easy to miss:
- The flaky test fails, somebody reruns it, and it passes.
- Rerunning becomes the standard response to any red run.
- A real regression appears, gets rerun, stays red, and gets labeled flaky.
- The suite is now decoration.
This is why nondeterministic dependencies get replaced with doubles rather than tolerated. Deleting the flaky test is also better than leaving it, since an honest gap in coverage is easier to reason about than a signal nobody believes.