Mocking & Patching
Replace external dependencies with controlled fakes using unittest.mock
Knowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
Mocking replaces a real dependency (database, API, clock) with a controlled fake so your unit test can run fast, in isolation, and predictably.
Mock and MagicMock:
python from unittest.mock import Mock, MagicMock m = Mock() m.return_value = 42 print(m()) # 42 m.some_method.return_value = "hello" print(m.some_method()) # "hello" MagicMock is like Mock but also implements Python's magic methods (__len__, __iter__, etc.) — use it when your code calls len()` on the mock or iterates over it.
Verifying calls:
python from unittest.mock import Mock send_email = Mock() notify_user(user, send_email) send_email.assert_called_once() send_email.assert_called_once_with(user.email, subject="Welcome!") print(send_email.call_count) # 1 print(send_email.call_args) # call(user.email, subject='Welcome!')
side_effect — raise exceptions or return different values per call:
python from unittest.mock import Mock # Raise an exception fetch = Mock(side_effect=ConnectionError("Network down")) # fetch() will raise ConnectionError # Return different values on successive calls counter = Mock(side_effect=[1, 2, 3]) print(counter()) # 1 print(counter()) # 2 print(counter()) # 3
@patch — replace an object for the duration of a test:
python from unittest.mock import patch # Patch where the name is USED, not where it's defined @patch("myapp.services.requests.get") def test_fetch_user(mock_get): mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"} mock_get.return_value.status_code = 200 result = fetch_user(1) # internally calls requests.get assert result["name"] == "Alice" mock_get.assert_called_once_with("https://api.example.com/users/1")
patch as context manager:
python from unittest.mock import patch def test_save_uses_current_time(): with patch("myapp.models.datetime") as mock_dt: mock_dt.now.return_value = datetime(2024, 1, 15, 12, 0, 0) record = create_record("test") assert record.created_at == datetime(2024, 1, 15, 12, 0, 0)
Patch where the name is used, not where it's defined:
If your module does from requests import get, patch myapp.module.get, not requests.get.
Examples
Mocking an external API call
The real network is never hit; the test runs in milliseconds and is deterministic regardless of external conditions
# src/weather.py
import requests
def get_temperature(city: str) -> float:
response = requests.get(f"https://api.weather.com/temp?city={city}")
return response.json()["temp"]
# tests/test_weather.py
from unittest.mock import patch
from src.weather import get_temperature
@patch("src.weather.requests.get")
def test_get_temperature(mock_get):
# Arrange — control what the fake API returns
mock_get.return_value.json.return_value = {"temp": 22.5}
mock_get.return_value.status_code = 200
# Act
result = get_temperature("London")
# Assert — verify the result and the call
assert result == 22.5
mock_get.assert_called_once_with(
"https://api.weather.com/temp?city=London"
)side_effect for error-path testing
side_effect with a list simulates different responses per call — ideal for testing retry logic
from unittest.mock import patch, Mock
from src.payment import process_payment, PaymentError
@patch("src.payment.stripe.charge")
def test_payment_retries_on_timeout(mock_charge):
# First call times out, second succeeds
mock_charge.side_effect = [
TimeoutError("Gateway timeout"),
{"id": "ch_123", "status": "succeeded"},
]
result = process_payment(amount=100, card="tok_visa")
assert result["status"] == "succeeded"
assert mock_charge.call_count == 2 # retried once
@patch("src.payment.stripe.charge")
def test_payment_raises_on_repeated_failure(mock_charge):
mock_charge.side_effect = TimeoutError("Gateway down")
with pytest.raises(PaymentError, match="Gateway down"):
process_payment(amount=100, card="tok_visa")How well did you understand this?
Next in Testing
Test-Driven Development