Better Exception Output in Django’s Test Runner With better-exceptions

The exceptions - They Live!

Today I learned about the better-exceptions pacakage. It makes exception output better, providing more context and colourization on the terminal.

If you’re using Django’s test framework, you can install better-exceptions during your test runs. It makes it the plain assert statement much more usable.

Plain asserts are clearer to write and read than the various self.assert* functions, so a definite win for tests. pytest’s assert statement rewriting is similar to better-exceptions, and it’s definitely a “killer feature” for pytest users. Whilst I recommend pytest, it can be hard to port existing projects, so using better-exceptions is a nice compromise.

Adding better-exceptions To Django Test Runs

First, you’ll want a custom test runner class. If you don’t already have one, create one as below, in a file like example/ Inside that the test runner’s run_tests() method, you can use a monkey-patch to install better-exceptions into the unittest TestResult class, which is responsible for output of tests. There’s a snippet in the better-exceptions documentation, which I’ve made Python-3-only. Putting it all together:

from unittest.result import TestResult

import better_exceptions
from django.test.runner import DiscoverRunner

class ExampleTestRunner(DiscoverRunner):
    def run_tests(self, *args, **kwargs):
        # Enable better-exceptions for better display of exceptions
        def exc_info_to_string(self, err, test):
            return "".join(better_exceptions.format_exception(*err))

        TestResult._exc_info_to_string = exc_info_to_string

        super().run_tests(*args, **kwargs)

Second, configure Django to use your test runner by setting TEST_RUNNER:

TEST_RUNNER = "example.test.ExampleTestRunner"

Then when you run tests, you should see nice colourized output. For example I made this broken test:

from django.test import SimpleTestCase

class BrokenTests(SimpleTestCase):
    def test_unequal(self):
        total = 1 + 1
        expected = 3
        assert total == expected

Running it I see this output, with better colourization in my terminal:

$ python test
System check identified no issues (0 silenced).
FAIL: test_unequal (example.core.tests.BrokenTests)
Traceback (most recent call last):
  File "/.../python3.9/unittest/", line 59, in testPartExecutor
  File "/.../python3.9/unittest/", line 593, in run
    │                    └ <bound method BrokenTests.test_unequal of <example.core.tests.BrokenTests testMethod=test_unequal>>
    └ <example.core.tests.BrokenTests testMethod=test_unequal>
  File "/.../python3.9/unittest/", line 550, in _callTestMethod
    └ <bound method BrokenTests.test_unequal of <example.core.tests.BrokenTests testMethod=test_unequal>>
  File "/.../example/core/", line 8, in test_unequal
    assert total == expected
           │        └ 3
           └ 2
AssertionError: assert total == expected

Ran 1 test in 0.049s

FAILED (failures=1)

Note the last frame, which shows that total is 2 and expected is 3.


If you like better-exceptions, also check out its documentation on use in your Django logging configuration.

May your tests be easy to read and write,


Make your development more pleasant with Boost Your Django DX.

Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts: