pytest-dev/pytest · error · ExceptionGroup

Unittest class cleanup errors

Error message

Unittest class cleanup errors

What it means

Raised as an ExceptionGroup by UnitClass.doClassCleanup / process_teardown_exceptions when unittest's tearDownClass + doClassCleanups produce more than one exception. pytest integrates with unittest's class-level cleanup machinery and bundles multiple captured exceptions (stored in cls.tearDown_exceptions) into one group so none are silently dropped, mirroring how it handles fixture teardown groups.

Source

Thrown at src/_pytest/unittest.py:148

        setup = getattr(cls, "setUpClass", None)
        teardown = getattr(cls, "tearDownClass", None)
        if setup is None and teardown is None:
            return None
        cleanup = getattr(cls, "doClassCleanups", lambda: None)

        def process_teardown_exceptions() -> None:
            # tearDown_exceptions is a list set in the class containing exc_infos for errors during
            # teardown for the class.
            exc_infos = getattr(cls, "tearDown_exceptions", None)
            if not exc_infos:
                return
            exceptions = [exc for (_, exc, _) in exc_infos]
            # If a single exception, raise it directly as this provides a more readable
            # error (hopefully this will improve in #12255).
            if len(exceptions) == 1:
                raise exceptions[0]
            else:
                raise ExceptionGroup("Unittest class cleanup errors", exceptions)

        def unittest_setup_class_fixture(
            request: FixtureRequest,
        ) -> Generator[None]:
            cls = request.cls
            if _is_skipped(cls):
                reason = cls.__unittest_skip_why__
                raise skip.Exception(reason, _use_item_location=True)
            if setup is not None:
                try:
                    setup()
                # unittest does not call the cleanup function for every BaseException, so we
                # follow this here.
                except Exception:
                    cleanup()
                    process_teardown_exceptions()
                    raise
            yield

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Drill into the group: `except ExceptionGroup as eg:` then iterate eg.exceptions (or use except* on 3.11+) to see each underlying failure.
  2. Make each addClassCleanup callback and tearDownClass body defensive (try/except inside), so one failure cannot mask others.
  3. Reproduce with `pytest --pdb` to drop into the first exception and inspect shared class state.
  4. Reorder cleanups so resource-owning teardowns run last (addClassCleanup is LIFO).

Example fix

// before
class MyTest(unittest.TestCase):
    @classmethod
    def tearDownClass(cls):
        close_pool()  # raises, masks cleanup errors
    # ...

// after
class MyTest(unittest.TestCase):
    @classmethod
    def tearDownClass(cls):
        try:
            close_pool()
        except Exception:
            pass  # log; let addClassCleanup callbacks still run
Defensive patterns

Strategy: try-catch

Validate before calling

import unittest

def run_class_cleanups_safely(cls: unittest.TestCase):
    cls.doClassCleanups()
    excs = getattr(cls, "tearDown_exceptions", []) or []
    if len(excs) > 1:
        raise BaseExceptionGroup("Unittest class cleanup errors", [e for (_, e, _) in excs])
    elif excs:
        raise excs[0][1]

Try / catch

try:
    run_unittest_class(cls)
except* Exception as eg:
    for e in eg.exceptions:
        log_class_cleanup(e)

Prevention

When it happens

Trigger: A unittest.TestCase subclass defines tearDownClass whose body raises, AND at least one addClassCleanup callback also raises; or multiple addClassCleanup callbacks each raise. pytest collects all exc_infos from doClassCleanups and wraps them when count > 1.

Common situations: Migrating unittest suites to pytest where class-level cleanups (DB rollback, temp file removal, mock stops) were not defensive; tearDownClass that asserts on shared state while cleanups also touch that state; flaky external services causing cascading cleanup failures.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/7c105812f8a95e0d.json. Report an issue: GitHub.