Thin-wrapper around the mock package for easier use with pytest
—
Advanced mock creation utilities and specialized mock objects for sophisticated testing scenarios. These capabilities extend beyond basic patching to provide spies, stubs, and automatic specification-based mocks.
Creates mocks that automatically match the interface of existing objects, providing better type safety and catching interface mismatches during testing.
def create_autospec(
self,
spec: Any,
spec_set: bool = False,
instance: bool = False,
**kwargs: Any
) -> MockType:
"""
Create a mock with automatic specification from an existing object.
Parameters:
- spec: Object to use as the specification template
- spec_set: If True, only allow access to attributes that exist on spec
- instance: If True, mock represents an instance of spec rather than spec itself
- **kwargs: Additional arguments passed to mock constructor
Returns:
Mock object that matches the spec's interface
"""Usage examples:
class DatabaseClient:
def connect(self, host: str, port: int) -> bool:
return True
def query(self, sql: str) -> List[Dict]:
return []
def close(self) -> None:
pass
def test_autospec_class(mocker):
# Create mock that matches DatabaseClient interface
mock_client = mocker.create_autospec(DatabaseClient)
mock_client.connect.return_value = True
mock_client.query.return_value = [{'id': 1, 'name': 'test'}]
# Mock enforces correct method signatures
result = mock_client.connect('localhost', 5432) # OK
data = mock_client.query('SELECT * FROM users') # OK
# This would raise AttributeError - method doesn't exist on spec
# mock_client.invalid_method()
mock_client.connect.assert_called_with('localhost', 5432)
def test_autospec_instance(mocker):
# Create mock representing an instance of DatabaseClient
mock_instance = mocker.create_autospec(DatabaseClient, instance=True)
mock_instance.connect.return_value = True
# Use like an instance
setup_database(mock_instance)
mock_instance.connect.assert_called()
def test_autospec_function(mocker):
def complex_function(a: int, b: str, c: bool = False) -> Dict:
return {'result': a + len(b)}
# Mock enforces function signature
mock_func = mocker.create_autospec(complex_function)
mock_func.return_value = {'result': 42}
# Correct call
result = mock_func(10, 'hello', c=True)
assert result == {'result': 42}
# This would raise TypeError - wrong signature
# mock_func(10) # Missing required argument 'b'Creates spy objects that call the original method while recording all interactions, perfect for verifying method calls without changing behavior.
def spy(self, obj: object, name: str) -> MockType:
"""
Create a spy that calls the original method and records calls.
The spy maintains the original behavior while providing mock
capabilities for verification and introspection.
Parameters:
- obj: Object containing the method to spy on
- name: Name of the method to spy on
Returns:
Mock object that wraps the original method
Spy attributes:
- spy_return: Return value of the last call
- spy_return_list: List of all return values
- spy_exception: Exception from last call (if any)
"""Usage examples:
class FileProcessor:
def process_file(self, filepath: str) -> Dict:
# Actually processes the file
with open(filepath, 'r') as f:
content = f.read()
return {'size': len(content), 'processed': True}
def test_spy_method_calls(mocker, tmp_path):
# Create test file
test_file = tmp_path / "test.txt"
test_file.write_text("Hello World")
processor = FileProcessor()
# Spy on the method - it still works normally
spy = mocker.spy(processor, 'process_file')
# Call the method - file is actually processed
result = processor.process_file(str(test_file))
# Verify the real behavior occurred
assert result['size'] == 11
assert result['processed'] is True
# Verify the spy recorded the call
spy.assert_called_once_with(str(test_file))
assert spy.spy_return == result
assert spy.spy_return_list == [result]
def test_spy_multiple_calls(mocker):
calculator = Calculator()
spy = mocker.spy(calculator, 'add')
# Make multiple calls
result1 = calculator.add(2, 3) # Returns 5
result2 = calculator.add(10, 20) # Returns 30
# Verify all calls were recorded
assert spy.call_count == 2
spy.assert_has_calls([
mocker.call(2, 3),
mocker.call(10, 20)
])
assert spy.spy_return_list == [5, 30]
def test_spy_exception_handling(mocker):
processor = FileProcessor()
spy = mocker.spy(processor, 'process_file')
# Call with invalid file - raises exception
with pytest.raises(FileNotFoundError):
processor.process_file('nonexistent.txt')
# Spy recorded the exception
assert isinstance(spy.spy_exception, FileNotFoundError)
spy.assert_called_once_with('nonexistent.txt')
async def test_async_spy(mocker):
class AsyncProcessor:
async def fetch_data(self, url: str) -> Dict:
# Actual async operation
await asyncio.sleep(0.1)
return {'url': url, 'status': 'success'}
processor = AsyncProcessor()
spy = mocker.spy(processor, 'fetch_data')
# Call the async method
result = await processor.fetch_data('https://api.example.com')
# Verify spy recorded the async call
assert result['status'] == 'success'
spy.assert_called_once_with('https://api.example.com')
assert spy.spy_return == resultCreates stub objects that accept any arguments and return configurable values, ideal for callback testing and placeholder implementations.
def stub(self, name: Optional[str] = None) -> unittest.mock.MagicMock:
"""
Create a stub method that accepts any arguments.
Parameters:
- name: Optional name for the stub (used in repr)
Returns:
MagicMock configured as a stub
"""
def async_stub(self, name: Optional[str] = None) -> AsyncMockType:
"""
Create an async stub method that accepts any arguments.
Parameters:
- name: Optional name for the stub (used in repr)
Returns:
AsyncMock configured as a stub
"""Usage examples:
def test_callback_with_stub(mocker):
# Create a stub for callback testing
callback = mocker.stub(name='data_callback')
# Configure the stub's behavior
callback.return_value = 'processed'
# Use stub in code that expects a callback
def process_data(data, callback_fn):
result = callback_fn(data)
return f"Result: {result}"
# Test with the stub
output = process_data({'id': 1}, callback)
assert output == "Result: processed"
callback.assert_called_once_with({'id': 1})
def test_multiple_callbacks(mocker):
# Create multiple stubs for different callbacks
on_start = mocker.stub(name='on_start')
on_complete = mocker.stub(name='on_complete')
on_error = mocker.stub(name='on_error')
# Configure different return values
on_start.return_value = None
on_complete.return_value = 'success'
on_error.return_value = None
# Use in code that takes multiple callbacks
processor = DataProcessor()
processor.process(
data={'items': [1, 2, 3]},
on_start=on_start,
on_complete=on_complete,
on_error=on_error
)
# Verify callback usage
on_start.assert_called_once()
on_complete.assert_called_once_with('success')
on_error.assert_not_called()
async def test_async_callback_stub(mocker):
# Create async stub for async callbacks
async_callback = mocker.async_stub(name='async_processor')
async_callback.return_value = {'status': 'completed'}
async def process_async(data, callback):
result = await callback(data)
return result['status']
# Test with async stub
status = await process_async({'id': 1}, async_callback)
assert status == 'completed'
async_callback.assert_called_once_with({'id': 1})
def test_flexible_stub_arguments(mocker):
# Stub accepts any arguments without complaint
flexible_stub = mocker.stub(name='flexible')
flexible_stub.return_value = 'ok'
# All these calls work - stub accepts anything
assert flexible_stub() == 'ok'
assert flexible_stub('arg') == 'ok'
assert flexible_stub('arg1', 'arg2') == 'ok'
assert flexible_stub(a=1, b=2) == 'ok'
assert flexible_stub('pos', keyword='value') == 'ok'
# Verify all calls were recorded
assert flexible_stub.call_count == 5Direct access to mock classes from the underlying mock module, providing convenient access to all mock types without importing unittest.mock.
# Mock class aliases (available as mocker attributes)
Mock: Type[unittest.mock.Mock]
MagicMock: Type[unittest.mock.MagicMock]
NonCallableMock: Type[unittest.mock.NonCallableMock]
NonCallableMagicMock: Type[unittest.mock.NonCallableMagicMock]
PropertyMock: Type[unittest.mock.PropertyMock]
AsyncMock: Type[unittest.mock.AsyncMock] # Python 3.8+
# Mock utilities
call: unittest.mock._Call
ANY: unittest.mock._AnyComparer
DEFAULT: unittest.mock._SentinelObject
sentinel: unittest.mock._SentinelObject
mock_open: Callable
seal: Callable # If availableUsage examples:
def test_direct_mock_creation(mocker):
# Create mocks directly from mocker attributes
magic_mock = mocker.MagicMock()
magic_mock.method.return_value = 42
# Use in your code
result = magic_mock.method('arg')
assert result == 42
# Mock is automatically registered for cleanup
magic_mock.method.assert_called_once_with('arg')
def test_property_mock(mocker):
class MyClass:
@property
def value(self):
return "real_value"
obj = MyClass()
# Mock the property
prop_mock = mocker.PropertyMock(return_value="mocked_value")
mocker.patch.object(MyClass, 'value', new_callable=lambda: prop_mock)
assert obj.value == "mocked_value"
prop_mock.assert_called_once()
def test_mock_constants(mocker):
# Use ANY to match any argument
mock_func = mocker.patch('mymodule.function')
mymodule.function('some_arg')
mock_func.assert_called_with(mocker.ANY)
# Use call to construct call objects
expected_calls = [
mocker.call('arg1'),
mocker.call('arg2')
]
mymodule.function('arg1')
mymodule.function('arg2')
mock_func.assert_has_calls(expected_calls)
def test_mock_open(mocker):
# Mock file operations
mock_file = mocker.mock_open(read_data="file content")
mocker.patch('builtins.open', mock_file)
with open('test.txt', 'r') as f:
content = f.read()
assert content == "file content"
mock_file.assert_called_once_with('test.txt', 'r')
def test_async_mock(mocker):
async def async_function():
return "async_result"
# Mock async function
mock_async = mocker.AsyncMock(return_value="mocked_result")
mocker.patch('mymodule.async_function', mock_async)
# Test async code
import asyncio
result = asyncio.run(mymodule.async_function())
assert result == "mocked_result"
mock_async.assert_awaited_once()Essential utilities for advanced mock assertions and configurations, providing fine-grained control over mock behavior and verification.
# Call construction and matching
call: unittest.mock._Call
ANY: unittest.mock._AnyComparer
DEFAULT: unittest.mock._SentinelObject
sentinel: unittest.mock._SentinelObject
mock_open: Callable
seal: Callable # If availableUsage examples:
def test_call_utility(mocker):
"""Using call to verify complex call patterns"""
mock_func = mocker.patch('mymodule.complex_function')
# Make multiple calls
mymodule.complex_function('arg1', key='value1')
mymodule.complex_function('arg2', key='value2')
# Verify specific calls
expected_calls = [
mocker.call('arg1', key='value1'),
mocker.call('arg2', key='value2')
]
mock_func.assert_has_calls(expected_calls)
def test_any_utility(mocker):
"""Using ANY to match flexible arguments"""
mock_func = mocker.patch('mymodule.function')
# Call with any arguments
mymodule.function('specific_arg', some_dynamic_value=42)
# Verify call with partial matching
mock_func.assert_called_with('specific_arg', some_dynamic_value=mocker.ANY)
# Mix specific and ANY arguments
mock_func.assert_called_with(mocker.ANY, some_dynamic_value=42)
def test_default_utility(mocker):
"""Using DEFAULT for partial patching"""
original_func = mymodule.function
# Patch with DEFAULT to preserve some behavior
mock_func = mocker.patch('mymodule.function')
mock_func.side_effect = lambda x: original_func(x) if x > 0 else mocker.DEFAULT
# DEFAULT returns the default mock behavior for negative values
result = mymodule.function(-1) # Returns DEFAULT mock behavior
assert isinstance(result, unittest.mock.MagicMock)
def test_sentinel_utility(mocker):
"""Using sentinel for unique sentinel values"""
# Create unique sentinel values for testing
MISSING = mocker.sentinel.MISSING
INVALID = mocker.sentinel.INVALID
mock_func = mocker.patch('mymodule.function')
mock_func.return_value = MISSING
result = mymodule.function()
assert result is MISSING # Identity comparison
assert result is not INVALID # Different sentinels
def test_mock_open_utility(mocker):
"""Using mock_open for file operations"""
# Mock reading a file
file_content = "line 1\nline 2\nline 3"
mock_file = mocker.mock_open(read_data=file_content)
mocker.patch('builtins.open', mock_file)
# Test reading
with open('test.txt', 'r') as f:
content = f.read()
lines = f.readlines() # This will be empty due to file pointer
assert content == file_content
mock_file.assert_called_once_with('test.txt', 'r')
# Test writing
mock_write_file = mocker.mock_open()
mocker.patch('builtins.open', mock_write_file)
with open('output.txt', 'w') as f:
f.write('test content')
mock_write_file.assert_called_once_with('output.txt', 'w')
mock_write_file().write.assert_called_once_with('test content')
def test_seal_utility(mocker):
"""Using seal to prevent mock attribute creation"""
if hasattr(mocker, 'seal'): # seal is only available in Python 3.7+
mock_obj = mocker.MagicMock()
mock_obj.existing_attr = 'value'
# Seal the mock to prevent new attribute creation
mocker.seal(mock_obj)
# Existing attributes still work
assert mock_obj.existing_attr == 'value'
# New attributes raise AttributeError
with pytest.raises(AttributeError):
_ = mock_obj.new_attrInstall with Tessl CLI
npx tessl i tessl/pypi-pytest-mock