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
| Feature | Mocking | Patching |
|---|---|---|
| Purpose | Simulate behavior or state of objects/functions | Temporarily replace objects/functions in their specific use context |
| Focus | Emphasizes behavior and interactions | Focuses on substituting specific components during test execution |
| Implementation | Typically implemented with mock libraries (e.g., unittest.mock) | Also implemented using the same mock libraries with methods like patch() |
| Isolation | Isolates a unit from external dependencies for testing | Isolates and replaces specific components temporarily |
| Flexibility | Offers flexibility in simulating various behaviors | Flexible in substituting and controlling components during test runtime |
| Replaces code | Replaces the behavior of the target object | Temporarily overrides the target object in its specific use context |
| Behavior control | High degree of control over the mocked object’s behavior | Allows control over the replaced object’s return value and side effects |
| Common Use Cases | Useful for testing complex interactions and dependencies | Ideal 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,
monkeypatchfixture orunittest.mock.patchfixtures. -
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()orassert_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.