CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pytest-mock

Thin-wrapper around the mock package for easier use with pytest

Pending
Overview
Eval results
Files

mock-creation.mddocs/

Mock Creation and Management

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.

Capabilities

Automatic Specification 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'

Spy Objects

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 == result

Stub Objects

Creates 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 == 5

Mock Class Aliases

Direct 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 available

Usage 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()

Mock Utilities

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 available

Usage 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_attr

Install with Tessl CLI

npx tessl i tessl/pypi-pytest-mock

docs

core-mocking.md

fixtures.md

index.md

mock-creation.md

tile.json