Lesson 6 of 9 · 20m
Test Doubles: Mocks, Stubs, and Fakes
The right double for the right job — mocks verify interaction, stubs return canned data, fakes provide real behavior, and over-mocking is the test smell that rots suites fastest.
Test doubles exist because real dependencies are slow, nondeterministic, or unavailable in a test environment. The mistake is defaulting to mocks — mock what you must, fake what you can, and real what you can afford.
The three kinds
- Stub — returns canned data:
PriceService.stub(get_price=10). Verifies your code handles the response. - Mock — records interactions and asserts them:
mock.send.assert_called_once_with(email). Verifies how your code talks to the dependency. - Fake — a working implementation that isn't production-grade: an in-memory database, a local mail spool, a fake clock. Verifies behavior without touching the real thing.
The over-mocking trap
# testing implementation, not behavior:
mock_repo.save.assert_called_with(user)
mock_email.send.assert_called_once()
mock_cache.set.assert_called_once()
# ... your test passes even if the code is completely wrongWhen every dependency is mocked, the test verifies the wiring matches the mock setup — not that the system works. Reserve mocks for side effects you can't verify otherwise (email actually sent, metric actually recorded) — and prefer asserting the observable outcome (the user got an email) over the interaction (send was called).
Fakes beat mocks for stateful dependencies
class FakeOrderRepo:
def __init__(self): self.orders = {}
def save(self, order): self.orders[order.id] = order
def get(self, id): return self.orders.get(id)A fake has real behavior — save then get returns what was saved, not MagicMock(). For a repository or cache, a fake is usually less code than the mock wiring and tests the same thing.
::exercise(pick-the-double)
Field notes
- If the test setup is longer than the test, the dependency is too coupled — that's a design signal.
pytest-mock,unittest.mock, or library-specific fakes (requests-mock, responses) — pick one per dependency type and be consistent.- Mocking a real DB is usually wrong — the mock can't teach you SQL semantics; use a real database in a transaction or a fake.