Python’s unittest.mock library provides powerful and flexible mocking capabilities, simplifying test setups compared to many other programming languages. However, ensuring that mocks remain isolated and do not inadvertently affect other parts of your tests is crucial for maintaining reliable test suites.
Example Scenario
Consider the following logging setup:
import logging
import sys
from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler
import settings
def setup_logging() -> None:
logger = logging.getLogger()
# Clear existing handlers to prevent duplicate logs
logger.handlers = []
logger.setLevel(settings.LOG_LEVEL)
handler_out = StructuredLogHandler(stream=sys.stdout)
logger.addHandler(handler_out)
# Manually configure handlers for loggers not inheriting from root
logger_gunicorn = logging.getLogger("gunicorn.error")
logger_gunicorn.handlers = []
logger_gunicorn.addHandler(handler_out)
We want to test whether both logger and logger_gunicorn have the handler correctly added.
Isolating Mocks in Pytest
To ensure each logger is mocked separately based on the logger’s name, use a defaultdict to manage multiple mock instances. Here’s how you can structure the test clearly with pytest:
import logging
from collections import defaultdict
from unittest.mock import MagicMock
from your_module import setup_logging
def test_setup_logging(mocker):
# Dictionary that creates a new MagicMock for each unique logger name
loggers: dict[str, MagicMock] = defaultdict(
lambda: mocker.MagicMock(spec=logging.Logger)
)
# Patch logging.getLogger to return a mock specific to the logger's name
mock_get_logger = mocker.patch(
"logging.getLogger", side_effect=lambda name=None: loggers[name]
)
setup_logging()
# Assert that each logger had 'addHandler' called exactly once
assert mock_get_logger().addHandler.call_count == 1
assert mock_get_logger("gunicorn.error").addHandler.call_count == 1
Explanation
defaultdict ensures each logger name generates an isolated MagicMock object.
The patched getLogger returns a mock based on the provided logger name.
Explicit assertions confirm the expected handler addition behavior.
This approach guarantees test isolation and accuracy, making your testing suite robust and maintainable.