Skip to content
intermediate Phase 5 · Python Testing

Mocking & Patching

Mock external dependencies with unittest.mock and pytest-mock.

1h
0 problems
Topic Progress 0%

Why Mocking?

Problems Without Mocking

# Code to test
def get_user_name(user_id):
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()['name']

# Problems:
# 1. Makes real HTTP calls (slow)
# 2. Depends on external service (unreliable)
# 3. Costs money (API calls)
# 4. Tests non-deterministic (network issues)
# 5. Cannot test error scenarios

Solutions with Mocking

from unittest.mock import Mock, patch

# Mock the external dependency
@patch('requests.get')
def test_get_user_name(mock_get):
    # Configure mock
    mock_get.return_value.json.return_value = {'name': 'Alice'}
    
    # Test with mock
    result = get_user_name(123)
    assert result == 'Alice'
    
    # Verify interactions
    mock_get.assert_called_once_with('https://api.example.com/users/123')

Types of Test Doubles

Type Description Use Case
Dummy Passed around, never used Fill parameters
Stub Returns predetermined values Provide test data
Mock Verifies interactions Check method calls
Spy Records calls, passes through Observe real behavior
Fake Working implementation Simplified database

Mock vs Stub

# Stub: Returns fixed value
def stub_get_user(user_id):
    return {'id': user_id, 'name': 'Test User'}

# Mock: Verifies interactions
mock = Mock()
mock.get_user(123)
assert mock.get_user.called
assert mock.get_user.call_args == ((123,),)

unittest.mock Basics

Mock Objects

from unittest.mock import Mock, MagicMock

# Create mock
mock_obj = Mock()

# Configure return values
mock_obj.method.return_value = 42
print(mock_obj.method())  # 42

# Configure side effects (exceptions)
mock_obj.method.side_effect = ValueError('Bad input')
mock_obj.method()  # Raises ValueError

# Configure side effects (multiple values)
mock_obj.method.side_effect = [1, 2, 3]
print(mock_obj.method())  # 1
print(mock_obj.method())  # 2
print(mock_obj.method())  # 3

Call Assertions

mock = Mock()

# Call the mock
mock('arg1', 'arg2')
mock('arg3', key='value')

# Assertions
assert mock.called
assert mock.call_count == 2
assert mock.call_args == (('arg3',), {'key': 'value'})
assert mock.call_args_list == [
    (('arg1', 'arg2'), {}),
    (('arg3',), {'key': 'value'})
]

# Reset mock
mock.reset_mock()
assert mock.call_count == 0

patch() Decorator

from unittest.mock import patch

# Patch where the object is used
@patch('module.function')
def test_something(mock_func):
    mock_func.return_value = 'mocked'
    result = function_using_func()
    assert result == 'mocked'

# Multiple patches
@patch('module.func1')
@patch('module.func2')
def test_multiple(mock2, mock1):  # Reversed order!
    mock1.return_value = 1
    mock2.return_value = 2
    assert function_using_both() == 3

# Context manager
with patch('module.function') as mock:
    mock.return_value = 'mocked'
    result = function_using_func()
    assert result == 'mocked'

MagicMock vs Mock

from unittest.mock import Mock, MagicMock

# Mock: Basic mock
mock = Mock()
mock.__len__.return_value = 5
len(mock)  # 5

# MagicMock: Supports magic methods
magic = MagicMock()
len(magic)  # 0 (default)
repr(magic)  # '<MagicMock id='...'>'
magic + 1  # MagicMock()
'hello' in magic  # True

Mocking Patterns

Mocking Database

from unittest.mock import Mock, patch

# Code to test
def create_user(db, name, email):
    if db.user_exists(email):
        raise ValueError('User already exists')
    user = db.insert_user(name, email)
    return user

# Test
def test_create_user():
    mock_db = Mock()
    mock_db.user_exists.return_value = False
    mock_db.insert_user.return_value = {'id': 1, 'name': 'Alice'}
    
    result = create_user(mock_db, 'Alice', 'alice@example.com')
    
    assert result == {'id': 1, 'name': 'Alice'}
    mock_db.user_exists.assert_called_once_with('alice@example.com')
    mock_db.insert_user.assert_called_once_with('Alice', 'alice@example.com')

# Test error case
def test_create_user_exists():
    mock_db = Mock()
    mock_db.user_exists.return_value = True
    
    with pytest.raises(ValueError) as exc_info:
        create_user(mock_db, 'Alice', 'alice@example.com')
    assert 'already exists' in str(exc_info.value)

Mocking File I/O

from unittest.mock import mock_open, patch

# Code to test
def read_config(filename):
    with open(filename) as f:
        return f.read()

# Test
def test_read_config():
    mock_data = '{"key": "value"}'
    with patch('builtins.open', mock_open(read_data=mock_data)) as mock_file:
        result = read_config('config.json')
        assert result == mock_data
        mock_file.assert_called_once_with('config.json')

Mocking HTTP Requests

import requests
from unittest.mock import patch, Mock

def fetch_user(user_id):
    response = requests.get(f'https://api.example.com/users/{user_id}')
    response.raise_for_status()
    return response.json()

# Test success
def test_fetch_user():
    with patch('requests.get') as mock_get:
        mock_response = Mock()
        mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        result = fetch_user(1)
        assert result == {'id': 1, 'name': 'Alice'}

# Test error
def test_fetch_user_not_found():
    with patch('requests.get') as mock_get:
        mock_response = Mock()
        mock_response.raise_for_status.side_effect = requests.HTTPError('404')
        mock_get.return_value = mock_response
        
        with pytest.raises(requests.HTTPError):
            fetch_user(999)

Best Practices

# ✅ Do:
# 1. Mock at the boundary (external services, I/O)
# 2. Use Mock/MagicMock for complex objects
# 3. Assert on interactions, not implementation
# 4. Keep tests simple and focused
# 5. Clean up mocks

# ❌ Don't:
# 1. Mock too much - test behavior, not implementation
# 2. Mock built-in functions unless necessary
# 3. Create overly complex mock setups
# 4. Forget to assert on mocks
# 5. Use mocks for simple value testing

Alternative: pytest-mock

# pytest-mock provides mocker fixture

def test_with_mocker(mocker):
    # mocker is a wrapper around unittest.mock
    mock_func = mocker.patch('module.function')
    mock_func.return_value = 'mocked'
    
    result = function_using_func()
    assert result == 'mocked'