Mocking
Most real programs talk to things outside your control: a web API, a database, the filesystem, the current time, a payment gateway. When you write a test for code that touches one of these, you don't actually want the test to make a real network call or write to a real database — that would make the test suite slow, flaky (it fails when the network is down or the API is rate-limiting you), and sometimes dangerous (accidentally charging a real credit card in a test run). Mocking replaces the real dependency with a fake stand-in object that behaves the way you tell it to, so the test only exercises the code you actually wrote.
Why mock?
Speed — a mocked API call returns instantly; a real one might take seconds.
Determinism — a mock always returns exactly what you configure, so the test doesn't depend on network conditions, server state, or the current date/time.
Isolation — you are testing your code's logic, not the reliability of a third-party service.
Cost and safety — no real charges, emails, or writes to production systems during test runs.
Mock and MagicMock
The standard library's unittest.mock module provides Mock, a generic object that records every attribute access and call made on it, and lets you pre-program return values. MagicMock is a subclass of Mock that also implements Python's "magic methods" (__len__, __iter__, __enter__, and so on), so it behaves like a normal object in more contexts.
from unittest.mock import Mock
fake_response = Mock()
fake_response.status_code = 200
fake_response.json.return_value = {"id": 1, "name": "Ada"}
print(fake_response.status_code) # 200
print(fake_response.json()) # {'id': 1, 'name': 'Ada'}Nothing about fake_response is a real HTTP response — it's an object that simply returns whatever you told it to when a particular attribute or method is accessed.
patch(): swapping out the real thing
unittest.mock.patch temporarily replaces an object (usually a function, class, or module attribute) with a Mock for the duration of a test, then automatically restores the original afterward. It can be used either as a decorator on the test function or as a context manager with with.
from unittest.mock import patch
# as a decorator
@patch("mymodule.requests.get")
def test_with_decorator(mock_get):
mock_get.return_value.status_code = 200
...
# as a context manager
def test_with_context_manager():
with patch("mymodule.requests.get") as mock_get:
mock_get.return_value.status_code = 200
...Worked example: mocking requests.get
Suppose you have a function that fetches a user's name from an API:
# user_service.py
import requests
def get_username(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()["name"]Testing this without mocking would require a real, reachable API. With patch, the test never touches the network:
# test_user_service.py
from unittest.mock import patch
from user_service import get_username
@patch("user_service.requests.get")
def test_get_username(mock_get):
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {"name": "Ada Lovelace"}
result = get_username(42)
assert result == "Ada Lovelace"
mock_get.assert_called_once_with("https://api.example.com/users/42")mock_get is a MagicMock standing in for requests.get. Setting mock_get.return_value.json.return_value configures what calling requests.get(...).json() returns, exactly matching the shape of a real requests.Response.
Asserting how a mock was called
Beyond controlling return values, mocks record every call made to them, which lets you assert your code called the dependency correctly — with the right arguments, the right number of times, and so on.
mock_get.assert_called_once() # called exactly once, any args
mock_get.assert_called_once_with( # called once with these exact args
"https://api.example.com/users/42"
)
mock_get.assert_called_with( # called with these args (last call)
"https://api.example.com/users/42"
)
print(mock_get.call_count) # how many times it was called