Testing Logger
Tags: python, testing, tdd, logging
Published: 2020-08-19 23:14:19

---
canonical_url: https://www.wilbertom.com/tutorial/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.**

```python
class Example:

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

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

```

```python

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.

```python

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.

```python

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 <a href="https://amzn.to/2Q7TfwB"> Python architecture book</a>. It is one of the best programming books I have read in a long time.

<a target="_blank"  href="https://www.amazon.com/gp/product/1492052205/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1492052205&linkCode=as2&tag=wilbertom-20&linkId=0bf0441103f5c75fd24e0eee42f22b21"><img border="0" src="//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=US&ASIN=1492052205&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL250_&tag=wilbertom-20" ></a><img src="//ir-na.amazon-adsystem.com/e/ir?t=wilbertom-20&l=am2&o=1&a=1492052205" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />

*As an Amazon Associate, I earn from qualifying purchases*