I am testing a function which calls loguru.logger.add("file.log")
at the start. This causes issues during pytest execution. The file is written to a temp dir and thus is being used by another process (good ol' Windows) when clean-up happens.
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'path/to/tmp_dir/file.log'
One solution is to patch loguru.logger.add
on each test. But this results in much repeated (boilerplate?) code. For many tests, I don't need to refer to logger.add
, just need patch it so the test runs.
@patch("loguru.logger.add")
def test_one(mock_logger_add):
...
# Useful to check, but don't want to call this in EVERY test
mock_logger_add.assert_called_once()
@patch("loguru.logger.add")
def test_two(mock_logger_add):
...
# No need to check mock_logger_add, just want my code to run
How can I reduce this duplication?
Things I've tried:
autouse=True
fixture to conftest.py
or in the test file/classe.g.
@pytest.fixture(autouse=True)
def patch_logger_add(monkeypatch):
monkeypatch.setattr("loguru.logger.add", lambda *args, **kwargs: None)
# or
# monkeypatch.setattr("loguru.logger.add", MagicMock())
or
@pytest.fixture(autouse=True)
def no_logger_add(monkeypatch):
monkeypatch.delattr("loguru.logger.add")
These don't work. Perhaps because, in order for loguru to work with pytest, we have to redefine caplog
and that involves calling logger.add
.
Note: I do not want to turn loguru off completely because I check the logs for errors in my tests.
compute_statistics.py
from loguru import logger
class ExampleClass:
def compute(self):
logger.add("0_example.log")
logger.info("Starting to compute")
logger.success("Finished computing")
return "Done"
conftest.py
import pytest
from loguru import logger
from _pytest.logging import LogCaptureFixture
@pytest.fixture
def caplog(caplog: LogCaptureFixture):
handler_id = logger.add(
caplog.handler,
format="{message}",
level=0,
filter=lambda record: record["level"].no >= caplog.handler.level,
enqueue=False, # Set to 'True' if your test is spawning child processes.
)
yield caplog
logger.remove(handler_id)
test_compute_statistics.py
import logging
import pytest
from compute_statistics import ExampleClass
from unittest.mock import patch
@pytest.fixture(autouse=True)
def patch_logger_add():
with patch("compute_statistics.logger.add"):
yield
def assert_no_errors_logged(caplog):
error_messages = [record for record in caplog.records if record.levelno >= logging.ERROR]
num_errors = len(error_messages)
assert num_errors == 0
def test_example_class(caplog):
ex = ExampleClass()
result = ex.compute()
assert result == "Done"
assert_no_errors_logged(caplog)
Since you say the patch
decorator works but monkeypatch
seemingly is not, I'm curious whether using patch
as a context manager from within the fixture may solve your issue?
Example
@pytest.fixture(autouse=True)
def patch_logger(caplog):
with patch("your_module.logger.add"):
yield