Run unittest cleanup even when setUp raises
Also published in our primary archive.
To run unittest cleanup when setUp() raises, register the cleanup with addCleanup() immediately after acquisition and before the operation that can fail. tearDown() is not called when setUp() fails, so putting the only release there leaves this particular path uncovered.
The fixture below records lifecycle events in an in-memory list. Its setUp() records acquisition, schedules release, then raises RuntimeError. The test body cannot run. The runner records one error, but the event list is ['acquired', 'released']; notably, it contains neither test-body nor tearDown.
The position of addCleanup() matters. If acquisition itself raises, no resource was obtained and no cleanup should be registered. Once acquisition succeeds, register cleanup before later setup work that might raise. Multiple cleanups are called in last-in, first-out order, which can be useful for nested resources.
The script observes runner state directly, not formatted test-runner output. It proves this one synthetic fixture executed its scheduled cleanup; it does not prove that an arbitrary cleanup function is correct or that an external resource was released. addCleanup is available in Python 3.1 and later. The tearDown documentation specifies that it runs only if setUp() succeeded.
AI assistance disclosure: this article and its synthetic example were prepared with AI assistance.
Example
import unittest
events = []
class SetupFailureTests(unittest.TestCase):
def setUp(self):
events.append("acquired")
self.addCleanup(self.release)
raise RuntimeError("setup failed")
def release(self):
events.append("released")
def tearDown(self):
events.append("tearDown")
def test_body(self):
events.append("test-body")
result = unittest.TestResult()
unittest.defaultTestLoader.loadTestsFromTestCase(SetupFailureTests).run(result)
assert len(result.errors) == 1
assert events == ["acquired", "released"]
print(f"errors: {len(result.errors)}")
print(f"events: {events}")
Expected output:
errors: 1
events: ['acquired', 'released']