DEV Community

Wilberto Morales
Wilberto Morales

Posted on • Originally published at wilbertom.com

Testing Logger

Logging is sometimes a part of your requirements. For example, you might want to issue a warning anytime some security event occurs. I like to test these events using a TestingLogger class that can stand in place of a real logger.

First, make your loggers parameters to your functions and classes. Globals make testing difficult.

class Example:

    def __init__(self, logger):
        self._logger = logger

    def do_something(self):
        self._logger.info("Logged something!")


def example(logger):
    logger.info("Logged something!")

Now you can use the TestingLogger during tests. If it quacks like a duck, its a duck. Let's make a TestingLogger that quacks like a logger.


class TestingLogger:
    def __init__(self):
        self.messages = []

    def debug(self, message):
        self._log(message)

    def info(self, message):
        self._log(message)

    def warning(self, message):
        self._log(message)

    def error(self, message):
        self._log(message)

    def critical(self, message):
        self._log(message)

    def exception(self, message):
        self._log(message)

    def _log(self, message):
        self.messages.append(message)

This class implements the methods you will call 99% of the time against a logger. Instead of logging though, it will capture the messages in a messages list. You can make assertions against this list in your tests.


def test_example_function():
    logger = TestingLogger()

    example(logger)

    assert "Logged something!" == logger.messages[0]


def test_example_class():
    logger = TestingLogger()

    Example(logger).do_something()

    assert "Logged something!" == logger.messages[0]

You can expand on this idea in many ways:

  • Capture the log level if you need to assert against them.
  • Inherit from list and use self.append to capture logs. Then you can make assertions against the testing logger directly instead of logger.messages.
  • Capture the str value of exceptions instead of the instances in TestingLogger.exception.
  • If passing loggers as parameters is cumbersome, you can create a default when one isn't specified, giving you flexibility in tests and convenience in the rest of the places.

If you would like to learn more about this testing style, check out the Python architecture book. It is one of the best programming books I have read in a long time.

As an Amazon Associate, I earn from qualifying purchases

Top comments (0)