Python mock tools replace selected dependencies during tests so the application can exercise controlled conditions. They can make failures reproducible and avoid live external actions, but an unconstrained mock can also make an impossible API appear to work. A passing test is useful only when the substitute reflects the relevant contract.
This guide explains patch location, specifications, return behavior, and assertions. The goal is to isolate the intended boundary without replacing so much of the system that the test merely confirms its own setup.
Choose the boundary from the test’s purpose
Identify what behavior the test needs to establish. A service’s response to a network timeout can use a controlled substitute, while an HTTP client’s serialization contract may need a different level of test.
Mock the dependency boundary rather than every internal helper. Excessive replacement can make refactoring break tests without changing behavior, or let a broken implementation pass because the meaningful work never runs.
Keep separate integration tests for the real dependency contract where appropriate. A unit test with a substitute cannot prove a remote API, database driver, or filesystem behaves exactly as assumed.
Patch where the object is looked up
Patch targets depend on the namespace used by the code under test. Replacing the original library name may not affect a module that imported the object into its own namespace.
Inspect the actual import and lookup path. The Python documentation’s where-to-patch guidance explains this distinction. Do not keep adding patches until the test happens to pass without understanding which reference is used.
Verify the substitute was actually called when that is relevant. A test can accidentally invoke a live dependency if the patch targeted the wrong location, creating unwanted network activity or external effects.
Use specs to constrain the substitute
A spec or spec_set can restrict attributes according to the supported behavior. Autospeccing can also preserve call signatures for appropriate objects. These tools help catch nonexistent methods and incorrectly shaped calls.
They do not validate every semantic requirement. A method with the right signature can still receive a wrong identifier or unauthorized value. Assert meaningful arguments and final outcomes separately.
Review dynamic APIs and introspection considerations before applying autospec mechanically. Some attributes appear only at runtime, and properties can have behavior during inspection. Choose a specification that reflects the actual supported interface.
Configure realistic return values
Set the return value expected by the code under test, including relevant structure and types. A default mock can manufacture nested attributes that make a broken access path appear valid.
If a class is replaced, distinguish the class mock from the instance returned by construction. Configure the instance methods the application actually uses. Confusing the two can leave the important behavior unconfigured.
Use small representative objects rather than arbitrary truthy placeholders. The application may depend on an empty collection, zero, false, or a missing field. Those cases deserve intentional fixtures.
Model failure through the real contract
Side effects can produce exceptions or a sequence of outcomes. Use the exception type and response shape the dependency really exposes, not a convenient generic error that follows a different application branch.
Test transient failure, permanent failure, and recovery where relevant. A retry test should verify the bounded retry behavior and final accepted outcome. Do not make the substitute succeed after unlimited attempts just to finish the test.
Include ambiguous outcomes for consequential actions. A timeout after an external action may differ from a failure before anything happened. The application’s recovery design should account for that distinction.
Handle asynchronous calls deliberately
Async dependencies need a substitute with appropriate await behavior, such as AsyncMock under supported runtime semantics. Calling a mock and awaiting it are different observations.
Use await-specific assertions where the contract requires them. A function can be called without the returned coroutine being awaited. An ordinary call-count assertion may miss that bug.
Test cancellation and exception propagation through the async boundary. A successful awaited result does not demonstrate cleanup or task ownership when the operation is interrupted.
Keep patches scoped and cleaned up
Context managers and decorators can restore patched objects when their supported scope ends. Keep the lifetime close to the test. A patch left active can make later tests depend on execution order.
If manually starting patchers, ensure teardown runs after failures. Use the test framework’s supported cleanup mechanism. Cleanup itself should not depend on the happy path reaching its last statement.
Review concurrency when tests share global patched objects. Two simultaneous tests can observe the same replaced namespace. Isolation policy should match the test runner’s execution model.
Assert meaningful behavior rather than every detail
Check important outputs, state changes, and boundary interactions. An exact call sequence is appropriate when order is part of the contract, but brittle assertions about unrelated internal steps can obstruct harmless refactoring.
Do not use call count alone as proof of correctness. The application may call the correct method with the wrong tenant, payload, or timeout. Inspect the arguments that carry the requirement.
Likewise, avoid asserting only the final mock value that the test itself configured. The result should demonstrate that the code performed the intended transformation, validation, or decision.
Compare substitutes with real integrations
Keep a small owned set of integration or contract checks for important dependencies. Confirm accepted argument shapes, error types, and serialization assumptions. Run them through an approved environment without live destructive effects.
When an integration changes, update both production code and relevant substitutes. A stale mock can keep a unit suite green after the actual API becomes incompatible.
For a payment-adapter unit test, substitute the approved client boundary, validate the idempotency key and amount, and model a timeout correctly. A separate sandbox contract test confirms the adapter’s real request behavior. Together they provide stronger evidence than either alone.
Frequently asked questions
Should I patch the original definition every time?
No. Patch the namespace where the code under test looks up the object.
Does autospec prove semantic correctness?
No. It helps constrain interface shape; arguments and outcomes still need checks.
Where are patching and async rules documented?
Read the unittest.mock reference for specifications, cleanup, and supported assertions.
For a complementary workflow, read pytest Monkeypatch: Isolated Tests With Clear Limits.