Custom equality, diffs and pytest interop

unittest · Expert · 10 min

Readable failures save debugging time, and most teams eventually run unittest code with pytest.

Failure output is configurable per TestCase:

  • maxDiff caps the length of diffs shown by assertEqual on long sequences or strings (default 640 characters). Set maxDiff = None in a class to always see the full diff.
  • longMessage (default True) appends your msg= to the standard message instead of replacing it.
  • addTypeEqualityFunc(MyType, func) registers a comparison function used by assertEqual when both arguments have exactly that type, so domain objects get domain-specific failure messages. The function should raise self.failureException(msg) on mismatch.
  • Custom assertion helpers: write methods that call the standard ones, and set __unittest = True at module level in a helper module to hide its frames from tracebacks (an undocumented but long-standing convention).

pytest interop: pytest collects TestCase subclasses natively, running setUp, cleanups, skip decorators and subTest. You can adopt pytest features gradually: plain assert with rich introspection, fixtures for new tests, plugins such as coverage and xdist. Some pytest features (fixtures injected as arguments, parametrize) don't work on TestCase methods, so new tests are usually written as plain functions.

Example

import unittest
from dataclasses import dataclass

@dataclass
class Money:
    amount: int
    currency: str

class TestMoney(unittest.TestCase):
    maxDiff = None

    def setUp(self):
        self.addTypeEqualityFunc(Money, self.assertMoneyEqual)

    def assertMoneyEqual(self, a, b, msg=None):
        if a.currency != b.currency:
            raise self.failureException(f"currency {a.currency} != {b.currency}")
        if a.amount != b.amount:
            raise self.failureException(f"{a.currency} amounts differ by {a.amount - b.amount}")

    def test_total(self):
        self.assertEqual(Money(1050, "EUR"), Money(1000, "EUR"))

unittest.main(exit=False)
# AssertionError: EUR amounts differ by 50

Remember

  • maxDiff = None shows full diffs; longMessage appends msg=
  • addTypeEqualityFunc gives assertEqual domain-specific messages
  • pytest runs TestCase classes unchanged, enabling gradual migration

Watch out for

  • Truncated diffs ('Diff is 1200 characters long') hiding the real difference; set maxDiff = None while debugging.

Check yourself

  1. What does addTypeEqualityFunc(Money, fn) change?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/unittest.html#unittest.TestCase.addTypeEqualityFunc