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
yieldView on GitHub (pinned to 98b357f69e)
Solutions
- Drill into the group: `except ExceptionGroup as eg:` then iterate eg.exceptions (or use except* on 3.11+) to see each underlying failure.
- Make each addClassCleanup callback and tearDownClass body defensive (try/except inside), so one failure cannot mask others.
- Reproduce with `pytest --pdb` to drop into the first exception and inspect shared class state.
- 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
- Keep tearDownClass and addClassCleanup callbacks small and defensive (try/except inside).
- Register cleanups in reverse order of resource acquisition (LIFO matches addClassCleanup).
- Avoid asserting shared state in tearDownClass; assert in the test body instead.
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
- errors while tearing down fixture "{self.argname}" of {node}
- errors during test teardown
- multiple thread exception warnings
- multiple unraisable exception warnings
- The fixture value for "{argname}" is not available during te
AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04).
Data as JSON: /data/errors/7c105812f8a95e0d.json.
Report an issue: GitHub.