suite teardown needs to be manually called to isolate failures simple test run catches errors and runs cleanup testsuite unittest testsuite testsuite addtesterrortestcaseraisingtest result self getrunner makeresult self assertequalresult errors testsuite runresult self assertequallenresult errors 1 traceback result errors0 self assertin exception debug bubbles up exceptions before cleanup traceback presetup assertcalledoncewith postteardown assertcalledoncewith def testrunpresetuperrorself presetup postteardown presetup sideeffect exceptionexception in presetup testsuite unittest testsuite testsuite addtesterrortestcasesimpletest result self getrunner makeresult self assertequalresult errors testsuite runresult self assertequallenresult errors 1 traceback result errors0 self assertinexception exception in presetup traceback presetup is called but not postteardown presetup assertcalledoncewith self assertfalsepostteardown called def testrunpostteardownerrorself presetup postteardown postteardown sideeffect exceptionexception in postteardown testsuite unittest testsuite testsuite addtesterrortestcasesimpletest result self getrunner makeresult self assertequalresult errors testsuite runresult self assertequallenresult errors 1 traceback result errors0 self assertinexception exception in postteardown traceback presetup and postteardwn are called presetup assertcalledoncewith postteardown assertcalledoncewith def testrunskippedtestnocleanupself presetup postteardown testsuite unittest testsuite testsuite addtesterrortestcaseskippedtest try testsuite runself getrunner makeresult except unittest skiptest self failskiptest should not be raised at this stage self assertfalsepostteardown called self assertfalsepresetup called def testdebugcleanupself presetup postteardown debug bubbles up exceptions before cleanup testsuite unittest testsuite testsuite addtesterrortestcaseraisingtest msg debug bubbles up exceptions before cleanup with self assertraisesmessageexception msg this is the same as testsuite debug result debugresult testsuite runresult debugtrue presetup is called but not postteardown presetup assertcalledoncewith self assertfalsepostteardown called self isolatedebugtesttestsuite result def testdebugbubblespresetuperrorself presetup postteardown this is the same as testsuite debug presetup is called but not postteardown debug bubbles up exceptions during postteardown msg exception in postteardown postteardown sideeffect exceptionmsg testsuite unittest testsuite testsuite addtesterrortestcasesimpletest with self assertraisesmessageexception msg this is the same as testsuite debug result debugresult testsuite runresult debugtrue presetup and postteardwn are called presetup assertcalledoncewith postteardown assertcalledoncewith self isolatedebugtesttestsuite result def testdebugskippedtestnocleanupself presetup postteardown testsuite unittest testsuite testsuite addtesterrortestcaseskippedtest with self assertraisesmessageunittest skiptest skip condition this is the same as testsuite debug result debugresult testsuite runresult debugtrue self assertfalsepostteardown called self assertfalsepresetup called self isolatedebugtesttestsuite result suite teardown needs to be manually called to isolate failures simple test run catches errors and runs cleanup pre setup is called but not post teardown pre setup and post teardwn are called simple debug run without errors debug bubbles up exceptions before cleanup this is the same as test_suite debug pre setup is called but not post teardown debug bubbles up exceptions during _pre_setup this is the same as test_suite debug pre setup is called but not post teardown debug bubbles up exceptions during _post_teardown this is the same as test_suite debug pre setup and post teardwn are called this is the same as test_suite debug
import unittest
from io import StringIO
from unittest import mock
from unittest.suite import _DebugResult

from django.test import SimpleTestCase


class ErrorTestCase(SimpleTestCase):
    def raising_test(self):
        self._pre_setup.assert_called_once_with()
        raise Exception("debug() bubbles up exceptions before cleanup.")

    def simple_test(self):
        self._pre_setup.assert_called_once_with()

    @unittest.skip("Skip condition.")
    def skipped_test(self):
        pass


@mock.patch.object(ErrorTestCase, "_post_teardown")
@mock.patch.object(ErrorTestCase, "_pre_setup")
class DebugInvocationTests(SimpleTestCase):
    def get_runner(self):
        return unittest.TextTestRunner(stream=StringIO())

    def isolate_debug_test(self, test_suite, result):
        
        test_suite._tearDownPreviousClass(None, result)
        test_suite._handleModuleTearDown(result)

    def test_run_cleanup(self, _pre_setup, _post_teardown):
        
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("raising_test"))
        result = self.get_runner()._makeResult()
        self.assertEqual(result.errors, [])
        test_suite.run(result)
        self.assertEqual(len(result.errors), 1)
        _, traceback = result.errors[0]
        self.assertIn(
            "Exception: debug() bubbles up exceptions before cleanup.", traceback
        )
        _pre_setup.assert_called_once_with()
        _post_teardown.assert_called_once_with()

    def test_run_pre_setup_error(self, _pre_setup, _post_teardown):
        _pre_setup.side_effect = Exception("Exception in _pre_setup.")
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("simple_test"))
        result = self.get_runner()._makeResult()
        self.assertEqual(result.errors, [])
        test_suite.run(result)
        self.assertEqual(len(result.errors), 1)
        _, traceback = result.errors[0]
        self.assertIn("Exception: Exception in _pre_setup.", traceback)
        
        _pre_setup.assert_called_once_with()
        self.assertFalse(_post_teardown.called)

    def test_run_post_teardown_error(self, _pre_setup, _post_teardown):
        _post_teardown.side_effect = Exception("Exception in _post_teardown.")
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("simple_test"))
        result = self.get_runner()._makeResult()
        self.assertEqual(result.errors, [])
        test_suite.run(result)
        self.assertEqual(len(result.errors), 1)
        _, traceback = result.errors[0]
        self.assertIn("Exception: Exception in _post_teardown.", traceback)
        
        _pre_setup.assert_called_once_with()
        _post_teardown.assert_called_once_with()

    def test_run_skipped_test_no_cleanup(self, _pre_setup, _post_teardown):
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("skipped_test"))
        try:
            test_suite.run(self.get_runner()._makeResult())
        except unittest.SkipTest:
            self.fail("SkipTest should not be raised at this stage.")
        self.assertFalse(_post_teardown.called)
        self.assertFalse(_pre_setup.called)

    def test_debug_cleanup(self, _pre_setup, _post_teardown):
        
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("simple_test"))
        test_suite.debug()
        _pre_setup.assert_called_once_with()
        _post_teardown.assert_called_once_with()

    def test_debug_bubbles_error(self, _pre_setup, _post_teardown):
        
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("raising_test"))
        msg = "debug() bubbles up exceptions before cleanup."
        with self.assertRaisesMessage(Exception, msg):
            
            result = _DebugResult()
            test_suite.run(result, debug=True)
        
        _pre_setup.assert_called_once_with()
        self.assertFalse(_post_teardown.called)
        self.isolate_debug_test(test_suite, result)

    def test_debug_bubbles_pre_setup_error(self, _pre_setup, _post_teardown):
        
        msg = "Exception in _pre_setup."
        _pre_setup.side_effect = Exception(msg)
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("simple_test"))
        with self.assertRaisesMessage(Exception, msg):
            
            result = _DebugResult()
            test_suite.run(result, debug=True)
        
        _pre_setup.assert_called_once_with()
        self.assertFalse(_post_teardown.called)
        self.isolate_debug_test(test_suite, result)

    def test_debug_bubbles_post_teardown_error(self, _pre_setup, _post_teardown):
        
        msg = "Exception in _post_teardown."
        _post_teardown.side_effect = Exception(msg)
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("simple_test"))
        with self.assertRaisesMessage(Exception, msg):
            
            result = _DebugResult()
            test_suite.run(result, debug=True)
        
        _pre_setup.assert_called_once_with()
        _post_teardown.assert_called_once_with()
        self.isolate_debug_test(test_suite, result)

    def test_debug_skipped_test_no_cleanup(self, _pre_setup, _post_teardown):
        test_suite = unittest.TestSuite()
        test_suite.addTest(ErrorTestCase("skipped_test"))
        with self.assertRaisesMessage(unittest.SkipTest, "Skip condition."):
            
            result = _DebugResult()
            test_suite.run(result, debug=True)
        self.assertFalse(_post_teardown.called)
        self.assertFalse(_pre_setup.called)
        self.isolate_debug_test(test_suite, result)