Django tests - patch object in all tests Django tests - patch object in all tests python python

Django tests - patch object in all tests


According to the mock documentation:

Patch can be used as a TestCase class decorator. It works bydecorating each test method in the class. This reduces the boilerplatecode when your test methods share a common patchings set.

This basically means that you can create a base test class with @patch decorator applied on it that would mock your external calls while every test method inside would be executed.

Also, you can use start() and stop() patcher's methods in setUp() and tearDown() methods respectively:

class BaseTestCase(TestCase):    def setUp(self):        self.patcher = patch('mymodule.foo')        self.mock_foo = self.patcher.start()    def tearDown(self):        self.patcher.stop()


Just to add to alecxe's answer, if you are using teardown() then according to the docs

you must ensure that the patching is “undone” by calling stop. This can be fiddlier than you might think, because if an exception is raised in the setUp then tearDown is not called.

If an exception is raised in your tests, your patching won't be undone. A better way would be to call addCleanup() inside your setUp(). Then you can omit the tearDown() method altogether.

class BaseTestCase(TestCase):    def setUp(self):        self.patcher = patch('mymodule.foo')        self.mock_foo = self.patcher.start()        self.addCleanup(self.patcher.stop) # add this line


I ended up creating a test runner to serve my purpose. I needed to mock the file storage so that images do not actually write to the file system while testing. The images object is being called in many tests thus patching each class would not be DRY. Also, I noticed that mocking the file itself would leave it on the system in case the test failed. But this method didn't.

I created a file runner.py in the project root

# runner.pyfrom unittest.mock import patchfrom django.test.runner import DiscoverRunnerfrom myapp.factories import ImageFactoryclass UnitTestRunner(DiscoverRunner):    @patch('django.core.files.storage.FileSystemStorage.save')    def run_tests(self, test_labels, mock_save, extra_tests=None, **kwargs):        mock_save.return_value = ImageFactory.get_image()        return super().run_tests(test_labels, extra_tests=None, **kwargs)

Then I would run my tests using python manage.py tests --testrunner=runner.UnitTestRunner


Just for clarity the ImageFactory.get_image method is a custom method

from django.core.files.base import ContentFilefrom factory.django import DjangoModelFactoryfrom io import BytesIOfrom PIL import Image as PilImagefrom random import randintclass ImageFactory(DjangoModelFactory):    @classmethod    def get_image(cls, name='trial', extension='png', size=None):        if size is None:            width = randint(20, 1000)            height = randint(20, 1000)            size = (width, height)        color = (256, 0, 0)        file_obj = BytesIO()        image = PilImage.new("RGBA", size=size, color=color)        image.save(file_obj, extension)        file_obj.seek(0)        return ContentFile(file_obj.read(), f'{name}.{extension}')