I am using a simple unit test based test runner to test my Django application.
My application itself is configured to use a basic logger in settings.py using:
<Is there a simple way to turn off logging in a global way, so that the application specific loggers aren't writing stuff out to the console when I run tests?
The other answers prevent "writing stuff out to the console" by globally setting the logging infrastructure to ignore anything. This works but I find it too blunt an approach. My approach is to perform a configuration change which does only what's needed to prevent logs to get out on the console. So I add a custom logging filter to my settings.py
:
from logging import Filter
class NotInTestingFilter(Filter):
def filter(self, record):
# Although I normally just put this class in the settings.py
# file, I have my reasons to load settings here. In many
# cases, you could skip the import and just read the setting
# from the local symbol space.
from django.conf import settings
# TESTING_MODE is some settings variable that tells my code
# whether the code is running in a testing environment or
# not. Any test runner I use will load the Django code in a
# way that makes it True.
return not settings.TESTING_MODE
And I configure the Django logging to use the filter:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'testing': {
'()': NotInTestingFilter
}
},
'formatters': {
'verbose': {
'format': ('%(levelname)s %(asctime)s %(module)s '
'%(process)d %(thread)d %(message)s')
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'filters': ['testing'],
'formatter': 'verbose'
},
},
'loggers': {
'foo': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
}
}
End result: when I'm testing, nothing goes to the console, but everything else stays the same.
I design code that contains logging instructions that are triggered only in specific circumstances and that should output the exact data I need for diagnosis if things go wrong. Therefore I test that they do what they are supposed to do and thus completely disabling logging is not viable for me. I don't want to find once the software is in production that what I thought would be logged is not logged.
Moreover, some test runners (Nose, for instance) will capture logs during testing and output the relevant part of the log together with a test failure. It is useful in figuring out why a test failed. If logging is completely turned off, then there's nothing that can be captured.
If you're using pytest
:
Since pytest captures log messages and only displays them for failed tests, you typically don't want to disable any logging. Instead, use a separate settings.py
file for tests (e.g., test_settings.py
), and add to it:
LOGGING_CONFIG = None
This tells Django to skip configuring the logging altogether. The LOGGING
setting will be ignored and can be removed from the settings.
With this approach, you don't get any logging for passed tests, and you get all available logging for failed tests.
The tests will run using the logging that was set up by pytest
. It can be configured to your liking in the pytest
settings (e.g., tox.ini
). To include debug level log messages, use log_level = DEBUG
(or the corresponding command line argument).
You can put this in the top level directory for unit tests __init__.py
file. This will disable logging globally in the unit test suite.
# tests/unit/__init__.py
import logging
logging.disable(logging.CRITICAL)
There is some pretty and clean method to suspend logging in tests with unittest.mock.patch
method.
foo.py:
import logging
logger = logging.getLogger(__name__)
def bar():
logger.error('There is some error output here!')
return True
tests.py:
from unittest import mock, TestCase
from foo import bar
class FooBarTestCase(TestCase):
@mock.patch('foo.logger', mock.Mock())
def test_bar(self):
self.assertTrue(bar())
And python3 -m unittest tests
will produce no logging output.
I am using a simple method decorator to disable logging only in a particular test method.
def disable_logging(f):
def wrapper(*args):
logging.disable(logging.CRITICAL)
result = f(*args)
logging.disable(logging.NOTSET)
return result
return wrapper
And then I use it as in the following example:
class ScenarioTestCase(TestCase):
@disable_logging
test_scenario(self):
pass
In cases where I wish to temporarily suppress a specific logger, I've written a little context manager that I've found useful:
from contextlib import contextmanager
import logging
@contextmanager
def disable_logger(name):
"""Temporarily disable a specific logger."""
logger = logging.getLogger(name)
old_value = logger.disabled
logger.disabled = True
try:
yield
finally:
logger.disabled = old_value
You then use it like:
class MyTestCase(TestCase):
def test_something(self):
with disable_logger('<logger name>'):
# code that causes the logger to fire
This has the advantage that the logger is re-enabled (or set back to its prior state) once the with
completes.