I want to get all logging output with mock. I searched, but only found ways to mock explicitly logging.info or logging.warn.
I need all output, whatever logging level was set.
def test_foo():
def my_log(...):
logs.append(...)
with mock.patch('logging.???', my_log):
...
In our libraries we use this:
import logging
logger=logging.getLogger(__name__)
def foo():
logger.info(...)
pytest
If you are writing your tests using pytest
, take a look at a neat fixture named caplog
that will capture log records for you. It captures all the emitted log records which you can then access via caplog.records
list. Each element is an instance of logging.LogRecord
, so you can easily access any of the LogRecord
s attributes. Example:
# spam.py
import logging
logger=logging.getLogger(__name__)
def foo():
logger.info('bar')
# tests.py
import logging
from spam import foo
def test_foo(caplog):
foo()
assert len(caplog.records) == 1
record = next(iter(caplog.records))
assert record.message == 'bar'
assert record.levelno == logging.INFO
assert record.module == 'spam'
# etc
The fixture was first introduced in a pytest
plugin named pytest-capturelog
which is now abandoned. Luckily, it got a decent fork named pytest-catchlog
, which has been merged into pytest==3.3.0
recently. So, if you use a recent version of pytest
, you are already good to go; for older versions of pytest
, install pytest-catchlog
from PyPI.
At the moment, pytest
doesn't provide any docs for the caplog
fixture (or at least I couldn't find any), so you can refer to pytest-catchlog
's documentation.
unittest
If pytest
is not an option, I wouldn't patch logging
at all - you can simply add a custom handler instead that will record all the incoming logs. A small example:
# utils.py
import logging
class RecordsCollector(logging.Handler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.records = []
def emit(self, record):
self.records.append(record)
# tests.py
import logging
import unittest
from utils import RecordsCollector
from spam import foo
class SpamTests(unittest.TestCase):
def setUp(self):
self.collector = RecordsCollector()
logging.getLogger('spam').addHandler(self.collector)
def tearDown(self):
logging.getLogger('spam').removeHandler(self.collector)
def test_foo(self):
foo()
# same checks as in the example above
self.assertEqual(len(self.collector.records), 1)
record = next(iter(self.collector.records))
self.assertEqual(record.message, 'bar')
self.assertEqual(record.levelno, logging.INFO)
self.assertEqual(record.module, 'spam')
if __name__ == '__main__':
unittest.main()
You can then extend the custom handler and implement any logic you need, like collecting the records in a dict
that maps log levels to lists of records, or add a contextmanager
implementation, so you can start and stop capturing records inside the test:
from contextlib import contextmanager
@contextmanager
def record_logs():
collector = RecordsCollector()
logging.getLogger('spam').addHandler(collector)
yield collector
logging.getLogger('spam').removeHandler(collector)
def test_foo(self):
with utils.record_logs() as collector:
foo()
self.assertEqual(len(collector.records), 1)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With