How should I verify a log message when testing Python code under nose?
From python 3.4 on, the standard unittest library offers a new test assertion context manager, assertLogs
. From the docs:
with self.assertLogs('foo', level='INFO') as cm: logging.getLogger('foo').info('first message') logging.getLogger('foo.bar').error('second message') self.assertEqual(cm.output, ['INFO:foo:first message', 'ERROR:foo.bar:second message'])
Fortunately this is not something that you have to write yourself; the testfixtures
package provides a context manager that captures all logging output that occurs in the body of the with
statement. You can find the package here:
http://pypi.python.org/pypi/testfixtures
And here are its docs about how to test logging:
UPDATE: No longer any need for the answer below. Use the built-in Python way instead!
This answer extends the work done in https://stackoverflow.com/a/1049375/1286628. The handler is largely the same (the constructor is more idiomatic, using super
). Further, I add a demonstration of how to use the handler with the standard library's unittest
.
class MockLoggingHandler(logging.Handler): """Mock logging handler to check for expected logs. Messages are available from an instance's ``messages`` dict, in order, indexed by a lowercase log level string (e.g., 'debug', 'info', etc.). """ def __init__(self, *args, **kwargs): self.messages = {'debug': [], 'info': [], 'warning': [], 'error': [], 'critical': []} super(MockLoggingHandler, self).__init__(*args, **kwargs) def emit(self, record): "Store a message from ``record`` in the instance's ``messages`` dict." try: self.messages[record.levelname.lower()].append(record.getMessage()) except Exception: self.handleError(record) def reset(self): self.acquire() try: for message_list in self.messages.values(): message_list.clear() finally: self.release()
Then you can use the handler in a standard-library unittest.TestCase
like so:
import unittestimport loggingimport fooclass TestFoo(unittest.TestCase): @classmethod def setUpClass(cls): super(TestFoo, cls).setUpClass() # Assuming you follow Python's logging module's documentation's # recommendation about naming your module's logs after the module's # __name__,the following getLogger call should fetch the same logger # you use in the foo module foo_log = logging.getLogger(foo.__name__) cls._foo_log_handler = MockLoggingHandler(level='DEBUG') foo_log.addHandler(cls._foo_log_handler) cls.foo_log_messages = cls._foo_log_handler.messages def setUp(self): super(TestFoo, self).setUp() self._foo_log_handler.reset() # So each test is independent def test_foo_objects_fromble_nicely(self): # Do a bunch of frombling with foo objects # Now check that they've logged 5 frombling messages at the INFO level self.assertEqual(len(self.foo_log_messages['info']), 5) for info_message in self.foo_log_messages['info']: self.assertIn('fromble', info_message)