How do you mock dependencies in tests using `unittest.mock`?
Quick Answer
`unittest.mock.Mock`/`MagicMock` create fake objects that record how they were called and can be configured to return specific values or raise exceptions; `patch()` (as a decorator or context manager) temporarily replaces a real object/function at a given import path with a mock for the duration of a test, then restores the original automatically. This isolates the code under test from slow, flaky, or unavailable real dependencies (network calls, databases, the current time).
Detailed Answer
Basic Mock: recording calls, configuring return values
from unittest.mock import Mock
mock_client = Mock()
mock_client.get_user.return_value = {"id": 1, "name": "Ada"}
result = mock_client.get_user(user_id=1)
result # {'id': 1, 'name': 'Ada'}
mock_client.get_user.assert_called_once_with(user_id=1) # verify how it was called
mock_client.get_user.call_count # 1
Mock (and MagicMock, which additionally supports dunder methods like
__len__/__iter__) auto-creates attributes/methods on access and
records every call made to them — assert_called_with,
assert_called_once, and .call_args/.call_args_list let you verify
the code under test interacted with the dependency correctly, not just
that it produced the right final output.
patch(): swapping out a real dependency temporarily
from unittest.mock import patch
# module: app/weather.py
import requests
def get_temperature(city):
resp = requests.get(f"https://api.weather.com/{city}")
return resp.json()["temp"]
# test
@patch("app.weather.requests.get") # patch WHERE IT'S USED, not where it's defined
def test_get_temperature(mock_get):
mock_get.return_value.json.return_value = {"temp": 72}
assert get_temperature("boston") == 72
mock_get.assert_called_once_with("https://api.weather.com/boston")
The critical rule: patch the name where it's looked up, not where
it's originally defined — app.weather.requests.get, because
app.weather imported requests and looks it up as requests.get in
its own namespace; patching requests.get globally would work too but is
broader and less precise than needed.
Context-manager form (for patching only part of a test)
def test_something():
with patch("app.weather.requests.get") as mock_get:
mock_get.return_value.json.return_value = {"temp": 72}
assert get_temperature("boston") == 72
# requests.get is back to normal here, outside the `with` block
Mocking a raised exception
@patch("app.weather.requests.get")
def test_get_temperature_handles_failure(mock_get):
mock_get.side_effect = ConnectionError("network down")
with pytest.raises(ConnectionError):
get_temperature("boston")
side_effect set to an exception class/instance makes the mock raise it
when called — the standard way to test error-handling paths without
needing to actually trigger a real failure (a downed network, a real
database outage).
spec/autospec: catching typos in mocked interfaces
from unittest.mock import create_autospec
mock_client = create_autospec(RealClient)
mock_client.get_uesr(1) # AttributeError -- typo caught immediately, unlike a bare Mock()
A bare Mock() accepts any attribute/method name silently, which can
hide a typo in test code (calling a method that doesn't actually exist on
the real object) until it breaks in production. create_autospec/
spec=RealClient constrains the mock to the real object's actual
interface, catching such mismatches at test time.
Interview-ready summary: Mock/MagicMock create call-recording fake
objects; patch() swaps a real dependency for a mock at the import path
where it's used, for the duration of a test. side_effect simulates
exceptions/varying return values across calls, and autospec/spec
constrain a mock to the real object's actual interface to catch typos
that a bare Mock() would silently accept.