I want to ensure that a certain condition in my code causes a log message to be written to the django log. How would I do this with the Django unit testing framework?
Is there a place where I can check logged messages, similarly to how I can check sent emails? My unit test extends django.test.TestCase
.
You can also use
assertLogs
fromdjango.test.TestCase
When you code is
This is the test code.
This lets you avoid patching just for logs.
If you're using test classes, you can use following solution:
This method replaces
error
function oflogging
module with your custom method only for test purposes and put stdout intocls.logger
variable which is available in every test case by callingself.logger
. At the end it reverts changes by placingerror
function fromlogging
module back.The common way of mocking out the logger object (see the splendid chap Simeon Visser's answer) is slightly tricky in that it requires the test to mock out the logging in all the places it's done. This is awkward if the logging comes from more than one module, or is in code you don't own. If the module the logging comes from changes name, it will break your tests.
The splendid 'testfixtures' package includes tools to add a logging handler which captures all generated log messages, no matter where they come from. The captured messages can later be interrogated by the test. In its simplest form:
Assuming code-under-test, which logs:
A test for this would be:
The word 'root' indicates the logging was sent via a logger created using
logging.getLogger()
(i.e. with no args.) If you pass an arg to getLogger (__name__
is conventional), that arg will be used in place of 'root'.The test does not care what module created the logging. It could be a sub-module called by our code-under-test, including 3rd party code.
The test asserts about the actual log message that was generated, as opposed to the technique of mocking, which asserts about the args that were passed. These will differ if the logging.info call uses '%s' format strings with additional arguments that you don't expand yourself (e.g. use
logging.info('total=%s', len(items))
instead oflogging.info('total=%s' % len(items))
, which you should. It's no extra work, and allows hypothetical future logging aggregation services such as 'Sentry' to work properly - they can see that "total=12" and "total=43" are two instances of the same log message. That's the reason why pylint warns about the latter form oflogging.info
call.)LogCapture includes facilities for log filtering and the like. Its parent 'testfixtures' package, written by Chris Withers, another splendid chap, includes many other useful testing tools. Documentation is here: http://pythonhosted.org/testfixtures/logging.html
Django has a nice context manager function called
patch_logger
.then in your test case:
where:
logger_name
is the logger name (duh)error
is the log levelcm
is the list of all log messagesMore details:
https://github.com/django/django/blob/2.1/django/test/utils.py#L638
It should work the same for django < 2.0, independently of python version (as long as it's supported by dj)
Using the
mock
module for mocking the logging module or the logger object. When you've done that, check the arguments with which the logging function is called.For example, if you code looks like this:
it would look like: