The unittest.mock module lets you replace parts of your system during tests. Essential for isolating units and testing code that depends on external services.
Basic Mock
from unittest.mock import Mock
# Create a mock object
mock = Mock()
# Call it with any arguments
result = mock(1, 2, key='value')
# Mock returns another Mock by default
print(result) # <Mock ...>
# Access attributes (also Mocks)
print(mock.some_attribute) # <Mock ...>
# Check how it was called
mock.assert_called_once_with(1, 2, key='value')
print(mock.call_count) # 1
print(mock.call_args) # call(1, 2, key='value')Configuring Return Values
from unittest.mock import Mock
# Set return value
mock = Mock(return_value=42)
print(mock()) # 42
# Different return values for consecutive calls
mock = Mock(side_effect=[1, 2, 3])
print(mock()) # 1
print(mock()) # 2
print(mock()) # 3
# Raise exception
mock = Mock(side_effect=ValueError("error"))
# mock() # Raises ValueError
# Custom function
mock = Mock(side_effect=lambda x: x * 2)
print(mock(5)) # 10MagicMock
Mock with magic methods pre-configured:
from unittest.mock import MagicMock
mock = MagicMock()
# Magic methods work
len(mock) # Returns 0 (default)
mock[0] # Returns MagicMock
mock.__str__() # '<MagicMock ...>'
# Configure magic methods
mock.__len__.return_value = 5
print(len(mock)) # 5
mock.__getitem__.return_value = 'value'
print(mock['key']) # 'value'patch Decorator
Replace objects during test:
from unittest.mock import patch
# Module: myapp.py
# import requests
# def fetch_data(url):
# response = requests.get(url)
# return response.json()
# Test file
@patch('myapp.requests.get')
def test_fetch_data(mock_get):
# Configure mock response
mock_response = Mock()
mock_response.json.return_value = {'key': 'value'}
mock_get.return_value = mock_response
# Call function
from myapp import fetch_data
result = fetch_data('http://api.example.com')
# Assert
mock_get.assert_called_once_with('http://api.example.com')
assert result == {'key': 'value'}patch as Context Manager
from unittest.mock import patch
def test_something():
with patch('myapp.external_service') as mock_service:
mock_service.call.return_value = 'mocked'
result = function_under_test()
assert result == 'expected'
mock_service.call.assert_called()patch.object
Patch attribute of an object:
from unittest.mock import patch
class MyClass:
def method(self):
return 'real'
obj = MyClass()
with patch.object(obj, 'method', return_value='mocked'):
print(obj.method()) # 'mocked'
print(obj.method()) # 'real'patch.dict
Patch dictionary values:
from unittest.mock import patch
import os
with patch.dict(os.environ, {'API_KEY': 'test_key'}):
print(os.environ['API_KEY']) # 'test_key'
# Clear values
with patch.dict(os.environ, {'DEBUG': '1'}, clear=True):
print(os.environ) # Only contains DEBUGAssertions
from unittest.mock import Mock, call
mock = Mock()
# Call the mock
mock(1, 2)
mock(3, 4)
mock('a', b='c')
# Assert calls
mock.assert_called() # Called at least once
mock.assert_called_once() # Fails (called 3 times)
mock.assert_called_with('a', b='c') # Last call matches
# Check all calls
assert mock.call_args_list == [
call(1, 2),
call(3, 4),
call('a', b='c'),
]
# Assert any call
mock.assert_any_call(3, 4)
# Assert not called
fresh_mock = Mock()
fresh_mock.assert_not_called()spec for Type Safety
from unittest.mock import Mock, create_autospec
class Database:
def connect(self): pass
def query(self, sql: str) -> list: pass
def close(self): pass
# Without spec: allows any attribute
mock = Mock()
mock.nonexistent() # Works (bad!)
# With spec: only allows existing attributes
mock = Mock(spec=Database)
mock.connect() # Works
# mock.nonexistent() # AttributeError!
# autospec also checks signatures
mock = create_autospec(Database)
mock.query("SELECT *") # Works
# mock.query() # TypeError: missing argumentPatching Where Imported
# myapp.py
from requests import get
def fetch():
return get('http://example.com')
# test_myapp.py
# WRONG: patches requests module, but myapp imported 'get' directly
@patch('requests.get')
def test_wrong(mock_get): pass
# RIGHT: patch where it's used
@patch('myapp.get')
def test_right(mock_get): passPropertyMock
Mock properties:
from unittest.mock import patch, PropertyMock
class MyClass:
@property
def value(self):
return expensive_calculation()
with patch.object(MyClass, 'value', new_callable=PropertyMock) as mock_value:
mock_value.return_value = 42
obj = MyClass()
print(obj.value) # 42AsyncMock (Python 3.8+)
from unittest.mock import AsyncMock, patch
import asyncio
async def async_function():
return 'real'
async def test_async():
mock = AsyncMock(return_value='mocked')
result = await mock()
assert result == 'mocked'
# With patch
with patch('myapp.async_function', new_callable=AsyncMock) as mock_func:
mock_func.return_value = 'mocked'
result = await async_function()Mock Methods Chaining
from unittest.mock import Mock
# Mock chained calls
mock = Mock()
mock.method1().method2().method3.return_value = 'result'
result = mock.method1().method2().method3
print(result) # 'result'
# Using configure_mock
mock = Mock()
mock.configure_mock(**{
'method.return_value.data': [1, 2, 3]
})
print(mock.method().data) # [1, 2, 3]reset_mock
from unittest.mock import Mock
mock = Mock(return_value=10)
mock(1, 2)
mock(3, 4)
# Reset call history
mock.reset_mock()
print(mock.call_count) # 0
print(mock()) # 10 (return_value preserved)
# Reset everything
mock.reset_mock(return_value=True)
print(mock()) # <Mock ...>Practical: Testing API Client
from unittest.mock import Mock, patch
import pytest
class APIClient:
def __init__(self, base_url: str):
self.base_url = base_url
def get_user(self, user_id: int) -> dict:
import requests
response = requests.get(f"{self.base_url}/users/{user_id}")
response.raise_for_status()
return response.json()
@patch('requests.get')
def test_get_user_success(mock_get):
# Arrange
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
mock_response.raise_for_status = Mock()
mock_get.return_value = mock_response
# Act
client = APIClient('http://api.example.com')
user = client.get_user(1)
# Assert
assert user == {'id': 1, 'name': 'Alice'}
mock_get.assert_called_once_with('http://api.example.com/users/1')
@patch('requests.get')
def test_get_user_error(mock_get):
mock_get.side_effect = requests.HTTPError("404")
client = APIClient('http://api.example.com')
with pytest.raises(requests.HTTPError):
client.get_user(999)Common Patterns
| Pattern | Use |
|---|---|
Mock(return_value=x) | Fixed return |
Mock(side_effect=[...]) | Sequential returns |
Mock(side_effect=func) | Dynamic return |
Mock(spec=Class) | Type-safe mock |
@patch('module.name') | Replace during test |
patch.object(obj, 'attr') | Patch instance |
patch.dict(d, values) | Patch dict |
create_autospec(Class) | Mock with signatures |
Mocking isolates your tests from external dependencies. Use it to make tests fast, deterministic, and focused.
React to this post: