AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
DashboardTestingMocking & Patching
TestingNot Started

Mocking & Patching

Replace external dependencies with controlled fakes using unittest.mock

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice
Knowledge
Fluency
Retention

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

Continue