How do you mock dependencies in tests using `unittest.mock`?

7 minintermediatetestingmockingunittest-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.