Controlling dependencies
Tests need to be fast and deterministic, but real dependencies like network calls and clocks are slow and unpredictable. Test doubles replace those dependencies with controlled stand ins so you can decide exactly what they return.
- Stub: returns canned values for inputs you care about.
- Mock: a stub that also records and asserts on how it was called.
- Fake: a lightweight working implementation, like an in memory store.
Mock the boundary, not the world
The art is choosing where to draw the line. Mock the external boundary, such as the network client, and let your own code run for real. Over mocking ties tests to internals and lets real bugs slip through, because the test only checks the mock.
- Replace the network so tests do not depend on a live server.
- Control time so timers and timeouts are deterministic.
- Verify a call happened only when that call is the behavior under test.
A good mock makes the test honest: it isolates the unit while still exercising the real logic you wrote. If a refactor breaks many mocks but no behavior, the mocks were too deep.
Key idea
Replace external boundaries with controlled test doubles to stay deterministic, but mock the edge rather than your own logic.