Course outline · 0% complete

0/29 lessons0%

Course overview →

Isolating the Unit

lesson 5-1 · ~9 min · 13/29

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.

your functionthe unit under testtest inputreal APIslow, flakytest doubleinstant, fixed answer
During a unit test the real dependency is unplugged and a double answers in its place.

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_temp that 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_email was called exactly once with a particular address

The choice follows from what the test is trying to claim:

DoubleUse it when you care aboutThe assert looks at
stubwhat the unit returnsthe return value
mockwhat the unit does to the outside worldthe recorded calls
fakethe unit needing a dependency that genuinely worksthe 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:

  1. The flaky test fails, somebody reruns it, and it passes.
  2. Rerunning becomes the standard response to any red run.
  3. A real regression appears, gets rerun, stays red, and gets labeled flaky.
  4. 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.