{"id":"7c105812f8a95e0d","repo":"pytest-dev/pytest","slug":"unittest-class-cleanup-errors","errorCode":null,"errorMessage":"Unittest class cleanup errors","messagePattern":"Unittest class cleanup errors","errorType":"exception","errorClass":"ExceptionGroup","httpStatus":null,"severity":"error","filePath":"src/_pytest/unittest.py","lineNumber":148,"sourceCode":"        setup = getattr(cls, \"setUpClass\", None)\n        teardown = getattr(cls, \"tearDownClass\", None)\n        if setup is None and teardown is None:\n            return None\n        cleanup = getattr(cls, \"doClassCleanups\", lambda: None)\n\n        def process_teardown_exceptions() -> None:\n            # tearDown_exceptions is a list set in the class containing exc_infos for errors during\n            # teardown for the class.\n            exc_infos = getattr(cls, \"tearDown_exceptions\", None)\n            if not exc_infos:\n                return\n            exceptions = [exc for (_, exc, _) in exc_infos]\n            # If a single exception, raise it directly as this provides a more readable\n            # error (hopefully this will improve in #12255).\n            if len(exceptions) == 1:\n                raise exceptions[0]\n            else:\n                raise ExceptionGroup(\"Unittest class cleanup errors\", exceptions)\n\n        def unittest_setup_class_fixture(\n            request: FixtureRequest,\n        ) -> Generator[None]:\n            cls = request.cls\n            if _is_skipped(cls):\n                reason = cls.__unittest_skip_why__\n                raise skip.Exception(reason, _use_item_location=True)\n            if setup is not None:\n                try:\n                    setup()\n                # unittest does not call the cleanup function for every BaseException, so we\n                # follow this here.\n                except Exception:\n                    cleanup()\n                    process_teardown_exceptions()\n                    raise\n            yield","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/unittest.py#L130-L166","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nclass MyTest(unittest.TestCase):\n    @classmethod\n    def tearDownClass(cls):\n        close_pool()  # raises, masks cleanup errors\n    # ...\n\n// after\nclass MyTest(unittest.TestCase):\n    @classmethod\n    def tearDownClass(cls):\n        try:\n            close_pool()\n        except Exception:\n            pass  # log; let addClassCleanup callbacks still run","handlingStrategy":"try-catch","validationCode":"import unittest\n\ndef run_class_cleanups_safely(cls: unittest.TestCase):\n    cls.doClassCleanups()\n    excs = getattr(cls, \"tearDown_exceptions\", []) or []\n    if len(excs) > 1:\n        raise BaseExceptionGroup(\"Unittest class cleanup errors\", [e for (_, e, _) in excs])\n    elif excs:\n        raise excs[0][1]","typeGuard":null,"tryCatchPattern":"try:\n    run_unittest_class(cls)\nexcept* Exception as eg:\n    for e in eg.exceptions:\n        log_class_cleanup(e)","preventionTips":["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."],"tags":["unittest","teardown","exceptiongroup","class-cleanup"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}