Opened 2 months ago

Last modified 5 weeks ago

#29024 assigned Bug

`TestContextDecorator` never exits if `setUp` fails in tests

Reported by: Anthony King Owned by: Shahbaj Sayyad
Component: Testing framework Version: master
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description (last modified by Anthony King)

when using TestContextDecorator as a class decorator, enable is called just before Test.setUp, and disable is called after Test.tearDown.

unfortunately, tearDown is not called in the event of a failure inside setUp. This can result in unexpected behaviour.

Even though this class is not documented, there are decorators that use it that are.


class example_decorator(TestContextDecorator):
    some_var = False

    def enable(self):
        self.__class__.some_var = True

    def disable(self):
        self.__class__.some_var = False

class Example1TestCase(TestCase):
    def test_var_is_false(self):
        # some_var will be False

class Example2TestCase(TestCase):
    def setUp(self):
        raise Exception

    def test_var_is_true(self):
        # won't be hit due to exception in setUp

class Example3TestCase(TestCase):
    def test_var_is_false(self):
        # some_var will be True now due to no cleanup in Example2TestCase


ERROR: test_var_is_true (example.tests.Example2TestCase)
... Traceback

FAIL: test_var_is_false (example.tests.Example3TestCase)
AssertionError: True is not false

Ran 3 tests in 0.007s

FAILED (failures=1, errors=1)

There are 2 potential fixes here:

  • decorate setUpClass and tearDownClass
  • use addCleanup inside the setUp decorator

addCleanup will always run, and will only ever be registered if the context manager was successfully enabled.

It would also be useful to have this class documented, however that's out of scope of this ticket.

Change History (6)

comment:1 Changed 2 months ago by Simon Charette

Triage Stage: UnreviewedAccepted
Version: 1.11master

Using addCleanup instead of hooking up on tearDown makes sense here.

comment:2 Changed 2 months ago by Shahbaj Sayyad

Owner: changed from nobody to Shahbaj Sayyad
Status: newassigned

comment:3 Changed 2 months ago by Anthony King

Description: modified (diff)

comment:4 Changed 5 weeks ago by Anthony King

Hi Shahbaj,

I saw your post in the mailing list.

So how we've done it internally is like this:

class TestContextDecorator(DjangoTestContextDecorator):
    def decorate_class(self, cls):

        if issubclass(cls, TestCase):
            decorated_setUp = cls.setUp

            def setUp(inner_self):
                context = self.enable()
                if self.attr_name:
                    setattr(inner_self, self.attr_name, context)

            cls.setUp = setUp
            return cls
        raise TypeError('Can only decorate subclasses of unittest.TestCase')

comment:5 Changed 5 weeks ago by Shahbaj Sayyad

Has patch: set

PR (no test)

Last edited 5 weeks ago by Tim Graham (previous) (diff)

comment:6 Changed 5 weeks ago by Tim Graham

Needs tests: set
Note: See TracTickets for help on using tickets.
Back to Top