The unittest.mock module lets you replace parts of your system during tests. Here's how to use it effectively.
Basic Mocking
from unittest.mock import Mock, MagicMock
# Create a mock object
mock = Mock()
# Call it like any object
mock.some_method(1, 2, 3)
mock.attribute = "value"
# Check what happened
mock.some_method.assert_called_once_with(1, 2, 3)
print(mock.attribute) # "value"
# MagicMock supports magic methods
magic = MagicMock()
print(len(magic)) # 0 (default)
magic.__len__.return_value = 5
print(len(magic)) # 5Return Values
from unittest.mock import Mock
mock = Mock()
# Simple return value
mock.method.return_value = 42
assert mock.method() == 42
# Different returns for multiple calls
mock.method.side_effect = [1, 2, 3]
assert mock.method() == 1
assert mock.method() == 2
assert mock.method() == 3
# Raise an exception
mock.method.side_effect = ValueError("error")
# mock.method() # Raises ValueError
# Dynamic return based on input
def dynamic_return(x):
return x * 2
mock.method.side_effect = dynamic_return
assert mock.method(5) == 10Patching
from unittest.mock import patch
# Patch a module attribute
@patch("mymodule.requests.get")
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {"status": "ok"}
result = mymodule.fetch_data()
mock_get.assert_called_once()
assert result["status"] == "ok"
# Context manager form
def test_with_context():
with patch("mymodule.requests.get") as mock_get:
mock_get.return_value.status_code = 200
# Test code here
# Decorator stacking (applied bottom-up)
@patch("mymodule.func_c")
@patch("mymodule.func_b")
@patch("mymodule.func_a")
def test_multiple(mock_a, mock_b, mock_c):
# mock_a patches func_a, etc.
passWhere to Patch
# mymodule.py
from os.path import exists
def check_file(path):
return exists(path)
# test_mymodule.py
from unittest.mock import patch
# WRONG: Patches os.path.exists globally
@patch("os.path.exists")
def test_wrong(mock_exists):
pass
# RIGHT: Patch where it's used (imported into mymodule)
@patch("mymodule.exists")
def test_right(mock_exists):
mock_exists.return_value = True
assert check_file("/some/path") == TrueSpec and Autospec
from unittest.mock import Mock, create_autospec
class RealClass:
def method(self, x: int) -> str:
return str(x)
# Without spec: accepts any attribute/method
mock = Mock()
mock.nonexistent_method() # Works (wrong!)
# With spec: only allows real attributes
mock = Mock(spec=RealClass)
# mock.nonexistent_method() # AttributeError
# Autospec: also checks signatures
mock = create_autospec(RealClass)
# mock.method() # TypeError: missing argument 'x'
mock.method(42) # Workspatch.object
from unittest.mock import patch
class MyClass:
def method(self):
return "real"
obj = MyClass()
# Patch instance method
with patch.object(obj, "method", return_value="mocked"):
assert obj.method() == "mocked"
# Patch class method
with patch.object(MyClass, "method", return_value="mocked"):
assert MyClass().method() == "mocked"patch.dict
from unittest.mock import patch
import os
# Patch environment variables
with patch.dict(os.environ, {"API_KEY": "test-key"}):
assert os.environ["API_KEY"] == "test-key"
# Clear and set
with patch.dict(os.environ, {"NEW_VAR": "value"}, clear=True):
assert "PATH" not in os.environ
assert os.environ["NEW_VAR"] == "value"Assertions
from unittest.mock import Mock, call
mock = Mock()
mock(1, 2, key="value")
mock(3, 4)
# Was called at all
mock.assert_called()
# Called at least once with specific args
mock.assert_any_call(1, 2, key="value")
# Most recent call
mock.assert_called_with(3, 4)
# Called exactly once
# mock.assert_called_once() # Fails: called twice
# Specific call count
assert mock.call_count == 2
# Check all calls
assert mock.call_args_list == [
call(1, 2, key="value"),
call(3, 4),
]
# Reset mock
mock.reset_mock()
assert mock.call_count == 0PropertyMock
from unittest.mock import patch, PropertyMock
class Config:
@property
def api_url(self):
return "https://api.example.com"
# Patch a property
with patch.object(
Config, "api_url", new_callable=PropertyMock
) as mock_url:
mock_url.return_value = "https://test.example.com"
config = Config()
assert config.api_url == "https://test.example.com"AsyncMock
from unittest.mock import AsyncMock, patch
import asyncio
async def fetch_data(client):
return await client.get("/data")
# Mock async functions
mock_client = AsyncMock()
mock_client.get.return_value = {"result": "ok"}
# Test async code
async def test_fetch():
result = await fetch_data(mock_client)
assert result == {"result": "ok"}
mock_client.get.assert_awaited_once_with("/data")
asyncio.run(test_fetch())
# Patch async function
@patch("mymodule.async_func", new_callable=AsyncMock)
async def test_with_patch(mock_func):
mock_func.return_value = "mocked"
result = await mymodule.async_func()
assert result == "mocked"Common Patterns
from unittest.mock import Mock, patch, ANY
# Accept any argument
mock = Mock()
mock("specific", ANY, key=ANY)
mock.assert_called_with("specific", ANY, key=ANY)
# Sentinel objects (unique placeholders)
from unittest.mock import sentinel
mock = Mock(return_value=sentinel.RESULT)
assert mock() is sentinel.RESULT
# Wrap real implementation
real_func = lambda x: x * 2
mock = Mock(wraps=real_func)
assert mock(5) == 10 # Calls real function
mock.assert_called_with(5) # But also tracks callsTesting External APIs
from unittest.mock import patch, Mock
import requests
def get_user(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()
@patch("requests.get")
def test_get_user(mock_get):
# Setup mock response
mock_response = Mock()
mock_response.json.return_value = {"id": 1, "name": "Test User"}
mock_response.raise_for_status = Mock()
mock_get.return_value = mock_response
# Test
user = get_user(1)
# Verify
mock_get.assert_called_once_with("https://api.example.com/users/1")
assert user["name"] == "Test User"
@patch("requests.get")
def test_get_user_error(mock_get):
mock_get.return_value.raise_for_status.side_effect = (
requests.HTTPError("404")
)
with pytest.raises(requests.HTTPError):
get_user(999)Testing with Databases
from unittest.mock import patch, MagicMock
class UserRepository:
def __init__(self, db):
self.db = db
def get_user(self, user_id):
return self.db.query(User).filter_by(id=user_id).first()
def test_get_user():
# Mock the database session
mock_db = MagicMock()
mock_user = User(id=1, name="Test")
mock_db.query.return_value.filter_by.return_value.first.return_value = mock_user
repo = UserRepository(mock_db)
user = repo.get_user(1)
assert user.name == "Test"
mock_db.query.assert_called_with(User)Best Practices
# 1. Use spec/autospec to catch API changes
mock = Mock(spec=RealClass)
# 2. Patch at the point of use, not definition
@patch("mymodule.dependency") # Not "dependency_module.func"
# 3. Keep patches minimal
# Bad: Mock entire class when you only need one method
# Good: Mock just the method you need
# 4. Verify interactions, not implementation
mock.assert_called() # Good: verifies behavior
# Checking internal state of mock: often too coupled
# 5. Use fixtures for common mocks (pytest)
@pytest.fixture
def mock_api():
with patch("mymodule.api_client") as mock:
mock.get.return_value = {"status": "ok"}
yield mockMocking isolates your tests from external dependencies. Use it to test your code's behavior, not the behavior of the things it calls.
React to this post: