diff --git a/django/test/utils.py b/django/test/utils.py index 85aa7e06c0..4f4ce99967 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -351,24 +351,15 @@ class TestContextDecorator: def decorate_class(self, cls): if issubclass(cls, TestCase): decorated_setUp = cls.setUp - decorated_tearDown = cls.tearDown def setUp(inner_self): context = self.enable() + inner_self.addCleanup(self.disable) if self.attr_name: setattr(inner_self, self.attr_name, context) - try: - decorated_setUp(inner_self) - except Exception: - self.disable() - raise - - def tearDown(inner_self): - decorated_tearDown(inner_self) - self.disable() + decorated_setUp(inner_self) cls.setUp = setUp - cls.tearDown = tearDown return cls raise TypeError('Can only decorate subclasses of unittest.TestCase') diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index cc7ea744a2..a7bd5575d1 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -484,6 +484,11 @@ Miscellaneous * The undocumented ``django.utils.http.limited_parse_qsl()`` function is removed. Please use :func:`urllib.parse.parse_qsl` instead. +* ``django.test.utils.TestContextDecorator`` now uses + :py:meth:`~unittest.TestCase.addCleanup` so that cleanups registered in the + :py:meth:`~unittest.TestCase.setUp` method are called before + ``TestContextDecorator.disable()``. + .. _deprecated-features-3.2: Features deprecated in 3.2 diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index a82dadceaa..87e2f56979 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1477,4 +1477,27 @@ class TestContextDecoratorTests(SimpleTestCase): self.assertFalse(mock_disable.called) with self.assertRaisesMessage(NotImplementedError, 'reraised'): decorated_test_class.setUp() + decorated_test_class.doCleanups() self.assertTrue(mock_disable.called) + + def test_cleanups_run_after_tearDown(self): + calls = [] + + class SaveCallsDecorator(TestContextDecorator): + def enable(self): + calls.append('enable') + + def disable(self): + calls.append('disable') + + class AddCleanupInSetUp(unittest.TestCase): + def setUp(self): + calls.append('setUp') + self.addCleanup(lambda: calls.append('cleanup')) + + decorator = SaveCallsDecorator() + decorated_test_class = decorator.__call__(AddCleanupInSetUp)() + decorated_test_class.setUp() + decorated_test_class.tearDown() + decorated_test_class.doCleanups() + self.assertEqual(calls, ['enable', 'setUp', 'cleanup', 'disable'])