nnov
nnov

Reputation: 541

How to properly use django testcase's addCleanup method?

I have a test subclass of APITestCase where I'm using the class-method setUpTestData to create data for my tests and also some mocks. Basically what I want to do is to run mock.patch.stopall (as shown next), but it's not working.

I have based my implementation on THIS ANSWER and I'm using: Django v2.2.4 and djangorestframework v3.10.2

import mock
from rest_framework.test import APITestCase


class FooTest(APITestCase):

    @classmethod
    def setUpTestData(cls):
        patcher_one = mock.patch('route.one')
        mock_route_one = patcher_one.start()

        patcher_two = mock.patch('route.two')
        mock_route_one = patcher_two.start()

        cls.addCleanup(mock.patch.stopall)

        # etc

        super(FooTest, cls).setUpTestData()

When running my tests with this code, I get:

TypeError: addCleanup() missing 1 required positional argument: 'function'

So I edit the addCleanup call to:

cls.addCleanup(function=mock.patch.stopall)

but I get the following:

TypeError: addCleanup() missing 1 required positional argument: 'self'

Editing to:

cls.addCleanup(cls, function=mock.patch.stopall)

I get

AttributeError: type object 'FooTest' has no attribute '_cleanups'

At this point I'm a bit lost.

The workaround that I'm using is to do it in the tearDownClass method:

@classmethod
def tearDownClass(cls):
    mock.patch.stopall()

But I would like to centralize all the testing logic in the setUpTestData method.

Anyone see's where I'm messing up?

Upvotes: 4

Views: 1398

Answers (2)

Dawid Dave Kosiński
Dawid Dave Kosiński

Reputation: 901

You cannot call an instance method without an instance. Django's setUpTestData is a classmethod.

code of addCleanUp (Django subclasses Unittest.TestCase):

def addCleanup(self, function, *args, **kwargs):
    """Add a function, with arguments, to be called when the test is
    completed. Functions added are called on a LIFO basis and are
    called after tearDown on test failure or success.

    Cleanup items are called even if setUp fails (unlike tearDown)."""
    self._cleanups.append((function, args, kwargs))

What you should do is move your mocks to the setup method. Firstly you should call self.addcleanup(patch.stopall) to make sure your mocks are stopped even when an error occurs in the setup method (sometimes it is possible), next you start your mock. This is explained in the python doc's here.

The following code should look something similar to this:

class FooTestCase(TestCase):
    def setUp(self):
        super().setUp()
        self.addCleanup(patch.stopall)
        patch('route.one').start()

Upvotes: 3

Harry Moreno
Harry Moreno

Reputation: 11603

the method name is setUp(self) you can't just change it because the test framework looks for this method to run.

change to

class FooTest(APITestCase):
    @classmethod
    def setUp(cls):
        ...
        cls.addCleanup(mock.patch.stopall)

    @classmethod
    def setUpTestData(cls):
        ...

Upvotes: -1

Related Questions