Isolating Dependencies with unittest.mock
Isolate external collaborators with Mock, side_effect, patch, and autospec while keeping the real behavior under test.
Intermediate
A unit test should usually exercise the behavior you own without depending on a live network, a paid service, a changing clock, or another system that is outside the test. Python’s built-in
unittest.mock module lets you replace a collaborator with a controlled test double and inspect important interactions. This lesson builds on the existing
unittest.TestCase lesson; it does not repeat how to create test cases or choose ordinary assertions.
Control a collaborator with Mock
A
Mock can return a value you choose and record how it was called. Passing a dependency into a function makes the boundary clear and often avoids patching module globals entirely. Test the result the user cares about, then check a collaborator call only when that interaction is part of the behavior.
Output
Ready to run.
The mock replaces only the lookup; the formatting function still runs for real. The test checks both the user-visible result and the input sent to the collaborator. A mock’s default return value is another mock, so set
return_value explicitly whenever the returned data matters.
Choose a return value
Which keyword gives a mock a controlled result when it is called?
from unittest.mock import Mock
lookup = Mock(={"name": "Ada"})
assert lookup("user-2") == {"name": "Ada"}
Simulate changing results and failures
Use
side_effect when a dependency must behave differently on successive calls, calculate a result from arguments, or raise a specific exception. An iterable is useful for modeling a temporary failure followed by success without contacting a real system. Keep the retry policy itself real so the test still checks your code.
Output
Ready to run.
An exception class or instance used as
side_effect is raised by the mock. An iterable supplies one outcome per call and raises
StopIteration if it runs out of outcomes; provide enough outcomes for the behavior under test. A callable
side_effect can instead inspect arguments and return a calculated value.
Model a dependency failure
Which setting makes this mock raise the supplied exception when called?
from unittest.mock import Mock
read = Mock(=TimeoutError("temporary"))
try:
read()
except TimeoutError:
result = "retry"
assert result == "retry"
Patch the name that the code looks up
Sometimes code imports or stores a dependency in a particular module or object namespace, so dependency injection is not available. In that case,
patch() or
patch.object() temporarily replaces the attribute the code actually looks up. For example, if
billing.py uses
billing.fetch_rate then patch that name—not the original provider’s name in another module. Patches are automatically undone when their decorator or context-manager scope ends, including when the block raises.
Output
Ready to run.
The small
gateway namespace is a stand-in for a dependency module:
total_in_euros() resolves
gateway.fetch_rate at call time, so that exact attribute is patched. In ordinary code, target the consumer’s lookup location. Prefer a scoped
with patch(...) or a
@patch(...) decorator over manually starting a patch and risking that it is never stopped.
Patch the looked-up attribute
Complete the temporary replacement of the attribute that this code calls.
from types import SimpleNamespace
from unittest.mock import patch
client = SimpleNamespace(load=lambda key: "live")
with patch.object(client, , return_value="sample") as mocked:
assert client.load("item-7") == "sample"
mocked.assert_called_once_with("item-7")
Constrain mocks with autospec
An unrestricted
Mock will happily invent attributes and accept arbitrary call signatures. That flexibility can hide misspellings in tests.
create_autospec() builds a mock from a real callable or object so function signatures are checked and unknown attributes are limited to the specification. The
autospec=True option offers the same idea when patching. A specification makes a mock safer, but it does not prove that a real external service behaves as expected.
Output
Ready to run.
Keep the real function signature
Which call is valid for a callable with signature `publish(channel, message, *, urgent=False)`?
from unittest.mock import create_autospec
def publish(channel, text, *, urgent=False):
pass
sender = create_autospec(publish)
sender()
sender.assert_called_once_with("news", "done", urgent=True)
Assert important interactions—and avoid over-mocking
Interaction assertions are valuable when a call itself matters: sending one notification, saving once, or not contacting a service for a rejected request.
assert_not_called() checks a negative interaction. Avoid asserting every incidental method call or replacing the code under test with a mock; over-mocked tests can pass while the real behavior is broken. Keep pure transformations real, mock boundaries that would be slow, unreliable, costly, or unsafe, and retain separate tests for integration with the real boundary.
Output
Ready to run.
Check that no call occurred
Which assertion verifies that a rejected record did not call the notification dependency?
from unittest.mock import Mock
send = Mock()
result = False
if result:
send("case-9")
send.()
Remember these boundaries
- Use Mock(return_value=...) for predictable collaborator results and assert meaningful calls.
- Use side_effect for outcomes that vary by call or for simulated exceptions.
- Patch the name the code under test actually looks up, inside a scope that restores it.
- Prefer autospec when it fits, and mock external boundaries rather than the behavior being tested.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.