How can I disable logging while running unit tests in Python Django?

68,728

Solution 1

logging.disable(logging.CRITICAL)

will disable all logging calls with levels less severe than or equal to CRITICAL. Logging can be re-enabled with

logging.disable(logging.NOTSET)

Solution 2

Since you are in Django, you could add these lines to your settings.py:

import sys
import logging

if len(sys.argv) > 1 and sys.argv[1] == 'test':
    logging.disable(logging.CRITICAL)

That way you don't have to add that line in every setUp() on your tests.

You could also do a couple of handy changes for your test needs this way.

There is another "nicer" or "cleaner" way to add specifics to your tests and that is making your own test runner.

Just create a class like this:

import logging

from django.test.simple import DjangoTestSuiteRunner
from django.conf import settings

class MyOwnTestRunner(DjangoTestSuiteRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):

        # Don't show logging messages while testing
        logging.disable(logging.CRITICAL)

        return super(MyOwnTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)

And now add to your settings.py file:

TEST_RUNNER = "PATH.TO.PYFILE.MyOwnTestRunner"
#(for example, 'utils.mytest_runner.MyOwnTestRunner')

This lets you do one really handy modification that the other approach doesn't, which is to make Django just tests the applications that you want. You can do that by changing the test_labels adding this line to the test runner:

if not test_labels:
    test_labels = ['my_app1', 'my_app2', ...]

Solution 3

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.

Why Do This?

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.

Solution 4

I like Hassek's custom test runner idea. It should be noted that DjangoTestSuiteRunner is no longer the default test runner in Django 1.6+, it has been replaced by the DiscoverRunner. For default behaviour, the test runner should be more like:

import logging

from django.test.runner import DiscoverRunner

class NoLoggingTestRunner(DiscoverRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):

        # disable logging below CRITICAL while testing
        logging.disable(logging.CRITICAL)

        return super(NoLoggingTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)

Solution 5

I've found that for tests within unittest or similar a framework, the most effective way to safely disable unwanted logging in unit tests is to enable/disable in the setUp/tearDown methods of a particular test case. This lets one target specifically where logs should be disabled. You could also do this explicitly on the logger of the class you're testing.

import unittest
import logging

class TestMyUnitTest(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)

    def tearDown(self):
        logging.disable(logging.NOTSET)
Share:
68,728

Related videos on Youtube

shreddd
Author by

shreddd

Updated on November 18, 2021

Comments

  • shreddd
    shreddd almost 2 years

    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:

    logging.basicConfig(level=logging.DEBUG)
    

    And in my application code using:

    logger = logging.getLogger(__name__)
    logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))
    

    However, when running unittests, I'd like to disable logging so that it doesn't clutter my test result output. 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?

    • dalore
      dalore over 8 years
      How did you enable logging whilst running tests? and why aren't you using django LOGGING?
  • CJ Gaconnet
    CJ Gaconnet over 12 years
    This may be obvious but I find it helpful to sometimes state the obvious for the benefit of other readers: You would put the call to logging.disable (from the accepted answer) at the top of tests.py in your application that is doing the logging.
  • shreddd
    shreddd over 12 years
    I ended up putting the call in setUp() but your point is well taken.
  • shreddd
    shreddd about 12 years
    Sure - putting it in settings.py would make it global.
  • qris
    qris almost 11 years
    in the setUp() method of your test, or in the actual test that generates the log messages that you want to hide.
  • mlissner
    mlissner over 10 years
    And in your tearDown() method: logging.disable(logging.NOTSET) puts the logging back in place neatly.
  • toabi
    toabi over 10 years
    Putting it in the init.py of the tests module is very useful.
  • Hassek
    Hassek over 9 years
    for Django 1.6+ please check @alukach answer.
  • Bunny Rabbit
    Bunny Rabbit over 8 years
    I found your solution after trying a lot of things. However I am not able to set the variable TEST_RUNNER in settings as its not able to import the module where the test_runner file is.
  • alukach
    alukach over 8 years
    Sounds like an import issue. Are you setting TEST_RUNNER to a string path to the runner (not the actual Python module)? Also, where is your runner located? I have mine in a separate app named helpers, which only has utils that don't import from anywhere else within the project.
  • Sardathrion - against SE abuse
    Sardathrion - against SE abuse almost 8 years
    Sometimes in unit tests, I want to assert that an error was logged thus this method is not ideal. Still, it is a good answer.
  • webtweakers
    webtweakers over 7 years
    "Any test runner I use will load the Django code in a way that makes it True." Interesting... How?
  • Louis
    Louis over 7 years
    I have a test_settings.py file which sits next to my project's settings.py. It is set to load settings.py and make some changes like set TESTING_MODE to True. My test runners are organized so that test_settings is the module loaded for the Django project settings. There are many ways this can be done. I usually go with setting the environment variable DJANGO_SETTINGS_MODULE to proj.test_settings.
  • rrauenza
    rrauenza over 7 years
    This is awesome and does exactly what I want. Hides the logging during unittests until something fails -- then Django Nose picks up the output and prints it with the failure. Perfect. Combine it with this to determine whether unit testing is active.
  • arauter
    arauter over 5 years
    Here is the documentation link for logging.disable(lvl) for a proper explanation. docs.python.org/2/library/logging.html#logging.disable
  • Rafael Beirigo
    Rafael Beirigo over 2 years
    One can also use setUpModule and tearDownModule to disable and enable the logging, respectively.
  • DanielM
    DanielM about 2 years
    This should be in manage.py, not in settings.py
  • flix
    flix almost 2 years
    I like this answer a lot, because it let you control the log level in the command line. This is useful when disabling logging for CI but keeping it enabled for running local tests. Thanks a lot.
  • Tim Tisdall
    Tim Tisdall almost 2 years
    I just wasted several hours trying to figure out why my assertLogs was failing and it was due to logging.disable(logging.CRITICAL). Just turning off console output in some way is FAR better than totally breaking logging.
  • Tim Tisdall
    Tim Tisdall almost 2 years
    There is no built-in TESTING_MODE (I just checked), so this need to be modified slightly. Could add the following into settings.py: TESTING_MODE = 'test' in sys.argv.
  • Tim Tisdall
    Tim Tisdall almost 2 years
    One point, though... The way Django documents setting up LOGGING is to define it completely and that overwrites the default set up. A person would need to make sure they redefined the 'console' handler in the same way so that it still has a filter to stop output when DEBUG=False.
  • Klaas van Schelven
    Klaas van Schelven almost 2 years
    @TimTisdall that is correct and a great addition. I forgot about that because I tend to set up my LOGGING by doing a deepcopy of the default and only changing those parts that I want to have changed.
  • cessor
    cessor almost 2 years
    Thank you, this is the solution I for one was looking for.