mock replaces collaborators to isolate the unit; use real implementations when the interaction is what you test
unittest.mock provides MagicMock and patch to replace objects during a test. The purpose is isolation: when you test a unit, you want failures to point at that unit's logic, not at a slow or unavailable collaborator. A mock also lets you assert interactions, such as that a payment provider was called exactly once with the right amount. The heuristic for choosing between mock and real is simple: mock when the collaborator is slow, non-deterministic, has side effects outside the test, or is not the thing under test. Use the real implementation when the interaction itself is the contract you are verifying, such as the SQL your repository generates or the exact bytes your serializer emits. Over-mocking is the most common failure mode: tests that assert on mock call shapes pass even when the real collaborator would reject the call.
Mock external services, clocks, randomness, filesystem paths outside tmp, and anything with side effects you do not want to trigger.
Prefer fakes (in-memory implementations of your own interface) over MagicMock for behavior-rich collaborators. Fakes catch interface drift, mocks do not.
Assert behavior sparingly. Asserting every call makes refactors painful and tests brittle.
Use autospec=True when mocking, so the mock enforces the real signature and catches wrong-argument errors.
Trade-off: mocks make tests fast and deterministic but reduce confidence in integration behavior. Pair them with a smaller set of integration tests.
Common mistake: patching the wrong symbol. Patch where the object is looked up, not where it is defined.
Common mistake: mocking the thing under test, which makes the test tautological.
Version note: unittest.mock has been in the stdlib since 3.3. autospec, spec_set, and AsyncMock (3.8+) are the modern features to use.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience