Feature

Mocking

Patching

Understanding Patching: How It Works

Patching temporarily replaces the actual implementation of a method, function, or object with a mock during testing.

Base:

# function_to_test.py
def send_email(email_address, message):
    email_service.send(email=email_address, message=message) 
from unittest.mock import Mock
import function_to_test
 
def test_send_email():
    mock_email_service = Mock()
    function_to_test.email_service = mock_email_service
    function_to_test.send_email("test@example.com", "Hello")
    mock_email_service.send.assert_called_with(email="test@example.com", message="Hello")

In the above code, we use the mocker fixture provided by pytest-mock to patch the email_service.send method in the function_to_test module. It automatically handles the start and end of the patching, meaning the original method is restored after the test, which helps prevent side effects on other tests.

TLDR

FeatureMockingPatching
PurposeSimulate behavior or state of objects/functionsTemporarily replace objects/functions in their specific use context
FocusEmphasizes behavior and interactionsFocuses on substituting specific components during test execution
ImplementationTypically implemented with mock libraries (e.g., unittest.mock)Also implemented using the same mock libraries with methods like patch()
IsolationIsolates a unit from external dependencies for testingIsolates and replaces specific components temporarily
FlexibilityOffers flexibility in simulating various behaviorsFlexible in substituting and controlling components during test runtime
Replaces codeReplaces the behavior of the target objectTemporarily overrides the target object in its specific use context
Behavior controlHigh degree of control over the mocked object’s behaviorAllows control over the replaced object’s return value and side effects
Common Use CasesUseful for testing complex interactions and dependenciesIdeal for controlling external dependencies like APIs during testing

Best Practices

Let’s quickly go through some essential best practices when using mocking or patching in your unit tests.

  • Use mocks and patches for external dependencies - Focus on isolating parts of the system that interact with external services like databases, web APIs, or third-party libraries.

  • Ensure that mocks mimic the behavior of the actual dependencies they replace as closely as possible.

  • Review API and other documentation regularly to ensure that mocks remain accurate and honour the contract.

  • Use tools that automatically unpatch or restore the original state of the object after each test to prevent side effects - Pytest fixtures with setup teardown, monkeypatch fixture or unittest.mock.patch fixtures.

  • Document why a mock or patch is necessary for understanding the context and purpose of the test with descriptive names.

  • When using mocking, verify the interaction between functions or objects, ensuring that the mock object was called with expected arguments. You can use methods like assert_called_once_with() or assert_called_with() as detailed in this guide.

  • Overusing mocking or patching may lead to overly complex tests and decrease the readability and maintainability of your codebase.