Doubles, patch & Test Seams
In short: a test double behaves like a real dependency but is
completely under the test's control — a seam is where you plug one in,
and the easiest seam is a parameter. unittest.mock.Mock invents any
attribute or call you touch, remembers everything (call_count,
call_args_list), and answers with return_value or a scripted
side_effect. When there's no parameter to pass a double through,
patch("dotted.name") swaps that name for a double for the length of a
with block — but a module's names live in its own dictionary, so the
rule every guide repeats is patch the name where it is looked up, not
where it is defined.

Quarry's fetch stage pulls exports from remote sources, retries the ones that time out, and stamps every report with the current time. Test it directly and your suite now depends on the network being up, the server being in a good mood, and the clock reading whatever it reads today — slow, flaky, and it can never check the timestamp because it's different every run. The fix: hand the code something that behaves like the network or the clock but is entirely under the test's control.
Replace the network with a fake you control
Here's Quarry's fetch loop. Notice what it does not do: it never opens a
connection itself. It's handed a source and calls source.get(name):
That source parameter is the seam. In production Quarry passes a real
HTTP source; in a test it passes a fake — no network, no waiting, and the
fake can produce a timeout on demand, the one thing a real server will
never do on request. Designing for a seam is most of the work of
testing; code that builds its own connection or reads the clock directly
can still be tested, but only by the heavier tool below.
People say "mock" for five different things, and the differences tell you
what a test is really checking: a dummy fills a parameter and is never
used; a stub returns canned answers; a fake is a working, simplified
implementation (FakeSource above); a spy also records how it was
called (FakeSource.calls); a mock is a spy you make assertions about
the calls on. Prefer checking the result when you can; check
interactions only for behaviour with no other visible trace, like a
retry.
Freeze the clock with unittest.mock
Writing a fake class for every collaborator gets tedious. Mock is an
object that accepts any attribute and any call, remembers all of them,
and returns whatever you configure:
source.get didn't exist until you touched it. return_value is the stub
half; call_count is the spy half; assert_called_with is the mock half.
from unittest.mock import Mock
fetch = Mock(return_value="ok")
fetch("orders")
fetch("stock")
print(fetch.call_count)To make a double raise, or answer differently each call, use side_effect
— a list is played back one item per call, and once it's exhausted the mock
raises StopIteration:
from unittest.mock import Mock
fetch = Mock(side_effect=["a", "b"])
fetch()
fetch()
fetch()The trap: a plain Mock invents any attribute you touch, so code calling a
method that doesn't exist on the real class still works against it. spec
ties the double to the real interface, and misspellings fail loudly:
A test that passes against a loose mock and fails in production is the
classic over-mocking bug — use spec whenever a double stands in for a
real class.
Patch the name the code actually looks up
Sometimes there's no seam. Quarry's report module reads the clock itself:
# quarry/report.py
from quarry.clock import now
def stamp():
return "report @ " + now()patch swaps a name for a double for the length of a with block, then
restores it. The hard part is choosing which name. A module's names live
in a dictionary, its __dict__. from quarry.clock import now does not
create a link to clock.now — it looks up the object clock.now currently
points at and copies that reference into report's own dictionary, under
report's own name now. From then on, the two modules hold two separate
names that happen to point at the same function.
Hence the rule: patch the name where it is looked up, not where it is defined. Here it is for real, with two in-memory modules:
The test patches the clock, yet the report still prints the real time. Change only the patch target so the report sees the fake.
patch.dict(d, {...}) temporarily changes entries in any dictionary —
including globals(), which patches a name in the code you're running
right now:
The second line proves the restore. (pytest's monkeypatch fixture does
the same job and undoes it automatically when the test ends.)
Quick practice
from unittest.mock import Mock
calls = []
def slow_sum(a, b):
calls.append((a, b))
return a + b
fake_sum = Mock(side_effect=slow_sum)
print(fake_sum(2, 3), fake_sum.call_count, calls)from unittest.mock import Mock
class Vault:
def save(self, row):
pass
double = Mock(spec=Vault)
try:
double.saev("x")
except AttributeError:
outcome = "rejected"
double.save("x")
print(outcome, double.save.call_count)Doubles matter in one number: if each real network call fails with
probability f, a suite making x real calls goes red for no reason with
probability 1 − (1 − f)ˣ. At 200 calls, a service failing one call in a
thousand turns the suite red 18% of the time — training everyone to ignore
red. And don't mock the thing you're testing: a test that patches
parse_line to test parse_line is testing the mock, not the code.
🎯 Your turn
Build fake_source(canned, names). Write a FakeSource whose get(name)
answers from canned, a dict of lists of responses: each call to a name
takes the next response from its list, and when only one is left it keeps
answering with that one (a server that stays down stays down). The response
"!timeout" means raise TimeoutError; a name not in canned raises
FileNotFoundError. Record every name asked for in calls.
Then drive Quarry's real fetch_all (it's in the starter) with it and
return {"results": ..., "calls": ...}:
fake_source({"orders": ["!timeout", "id,total"]}, ["orders"])
# → {"results": {"orders": "id,total"}, "calls": ["orders", "orders"]}