Real tests don't hit production databases. They don't call external APIs. They don't send actual emails. Mocking lets you replace these dependencies with controlled substitutes.
The Problem
Consider this function:
def get_user_weather(user_id):
user = database.get_user(user_id)
weather = requests.get(f"https://api.weather.com/{user.city}")
return f"{user.name}'s weather: {weather.json()['temp']}°F"Testing this directly would require a database, network access, and a weather API key. That's fragile and slow. Instead, we mock.
unittest.mock Basics
Python's built-in unittest.mock module provides everything you need:
from unittest.mock import Mock, patch, MagicMock
# Create a simple mock
mock_user = Mock()
mock_user.name = "Alice"
mock_user.city = "Seattle"
# Mock methods return mocks by default
mock_user.get_preferences() # Returns another MockPatching with @patch
The @patch decorator replaces objects during a test:
from unittest.mock import patch
@patch("myapp.services.requests.get")
@patch("myapp.services.database.get_user")
def test_get_user_weather(mock_get_user, mock_requests_get):
# Configure mocks
mock_get_user.return_value = Mock(name="Alice", city="Seattle")
mock_requests_get.return_value.json.return_value = {"temp": 72}
# Run the actual function
result = get_user_weather(123)
# Verify behavior
assert result == "Alice's weather: 72°F"
mock_get_user.assert_called_once_with(123)
mock_requests_get.assert_called_once_with("https://api.weather.com/Seattle")Important: Patch where the object is used, not where it's defined. If myapp/services.py imports requests, patch myapp.services.requests, not requests.
pytest-mock: A Better Interface
The pytest-mock plugin provides a cleaner fixture-based API:
pip install pytest-mockdef test_get_user_weather(mocker):
# mocker is automatically available as a fixture
mock_get_user = mocker.patch("myapp.services.database.get_user")
mock_requests = mocker.patch("myapp.services.requests.get")
mock_get_user.return_value = mocker.Mock(name="Alice", city="Seattle")
mock_requests.return_value.json.return_value = {"temp": 72}
result = get_user_weather(123)
assert result == "Alice's weather: 72°F"The mocker fixture automatically cleans up patches after each test.
Mocking Return Values and Side Effects
Control what mocks return:
def test_with_return_value(mocker):
mock_func = mocker.patch("myapp.calculate")
# Single return value
mock_func.return_value = 42
# Different values on consecutive calls
mock_func.side_effect = [1, 2, 3]
assert mock_func() == 1
assert mock_func() == 2
assert mock_func() == 3
# Raise an exception
mock_func.side_effect = ValueError("Something went wrong")
# Custom logic
mock_func.side_effect = lambda x: x * 2Assertions on Mock Calls
Verify your code called dependencies correctly:
def test_call_assertions(mocker):
mock_send = mocker.patch("myapp.notifications.send_email")
# Run code that should send email
process_order(order_id=123)
# Verify it was called
mock_send.assert_called()
mock_send.assert_called_once()
mock_send.assert_called_with(
to="customer@example.com",
subject="Order Confirmed"
)
# Check call count
assert mock_send.call_count == 1
# Inspect call arguments
args, kwargs = mock_send.call_args
assert kwargs["to"] == "customer@example.com"MagicMock for Special Methods
MagicMock supports Python's magic methods:
def test_context_manager(mocker):
mock_file = mocker.MagicMock()
mock_file.__enter__.return_value = mock_file
mock_file.read.return_value = "file contents"
mocker.patch("builtins.open", return_value=mock_file)
with open("test.txt") as f:
content = f.read()
assert content == "file contents"Partial Mocking with wraps
Sometimes you want to spy on real objects:
def test_spy_on_method(mocker):
real_calculator = Calculator()
# Spy on the method - calls real implementation but tracks calls
spy = mocker.spy(real_calculator, "add")
result = real_calculator.add(2, 3)
assert result == 5 # Real result
spy.assert_called_once_with(2, 3) # But we tracked the callMocking Properties
Use PropertyMock for properties:
def test_mock_property(mocker):
mock_user = mocker.Mock()
type(mock_user).is_admin = mocker.PropertyMock(return_value=True)
assert mock_user.is_admin == TrueMocking Async Functions
For async code, use AsyncMock:
from unittest.mock import AsyncMock
async def test_async_function(mocker):
mock_fetch = mocker.patch(
"myapp.api.fetch_data",
new_callable=AsyncMock
)
mock_fetch.return_value = {"data": "value"}
result = await process_data()
mock_fetch.assert_awaited_once()Common Patterns
Mocking datetime
def test_with_frozen_time(mocker):
mock_datetime = mocker.patch("myapp.utils.datetime")
mock_datetime.now.return_value = datetime(2026, 3, 22, 12, 0, 0)
assert get_greeting() == "Good afternoon!"Mocking Environment Variables
def test_with_env_var(mocker):
mocker.patch.dict("os.environ", {"API_KEY": "test-key"})
client = create_api_client()
assert client.api_key == "test-key"Mocking Class Instances
def test_mock_class(mocker):
MockDatabase = mocker.patch("myapp.Database")
mock_instance = MockDatabase.return_value
mock_instance.query.return_value = [{"id": 1}]
results = fetch_users()
assert results == [{"id": 1}]When Not to Mock
Mocking is powerful but has limits:
- Don't mock the system under test
- Don't mock simple data objects—use real ones
- Don't mock so much that tests pass with broken code
- Consider integration tests for critical paths
Mock at the boundaries: external APIs, databases, file systems, time. Let your internal logic run for real. That's where the bugs hide.