Тесты Django - объект патча во всех тестах

Мне нужно создать какой-то MockMixin для моих тестов. Он должен включать в себя mocks для всего, что вызывает внешние источники. Например, каждый раз, когда я сохраняю модель в панели администратора, я вызываю несколько удаленных URL-адресов. Было бы хорошо, чтобы это насмехалось и использовало так:

class ExampleTestCase(MockedTestCase):
    # tests

Поэтому каждый раз, когда я сохраняю модель в admin, например, в функциональных тестах, этот макет применяется вместо вызова удаленных URL-адресов.

Это действительно возможно? Я могу сделать это для одного конкретного теста, это не проблема. Но было бы более полезно иметь какой-то глобальный макет, потому что я использую его много.

Ответ 1

Согласно mock документации:

Патч может использоваться как декоратор класса TestCase. Он работает, украшая каждый метод тестирования в классе. Это уменьшает код шаблона, когда ваши методы тестирования имеют общие патчи.

Это в основном означает, что вы можете создать базовый тестовый класс с нанесенным на него декоратором @patch который будет издеваться над вашими внешними вызовами, в то время как каждый тестовый метод внутри будет выполнен.

Кроме того, вы можете использовать методы start() и stop() patcher в методах setUp() и tearDown() соответственно:

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

    def tearDown(self):
        self.patcher.stop()

Ответ 2

Просто добавьте в alecxe ответ, если вы используете teardown(), то в соответствии с документами

Вы должны убедиться, что исправление "отменено", вызвав stop. Это может быть сложнее, чем вы думаете, потому что если в setUp возникает исключение, то tearDown не вызывается.

Если в ваших тестах возникает исключение, исправление не будет отменено. Лучшим способом было бы позвонить addCleanup() внутри вашего setUp(). Тогда вы можете вообще опустить метод tearDown().

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

Ответ 3

Я закончил тем, что создал тестового бегуна, чтобы служить моей цели. Мне нужно было смоделировать хранилище файлов, чтобы изображения не записывались в файловую систему во время тестирования. Объект images вызывается во многих тестах, поэтому исправление каждого класса не будет DRY. Кроме того, я заметил, что подделка самого файла оставит его в системе в случае провала теста. Но этот метод не сделал.

Я создал файл runner.py в корне проекта

# runner.py
from unittest.mock import patch

from django.test.runner import DiscoverRunner

from myapp.factories import ImageFactory


class 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)

Затем я запускаю свои тесты, используя python manage.py tests --testrunner=runner.UnitTestRunner


Просто для наглядности метод ImageFactory.get_image - это пользовательский метод

from django.core.files.base import ContentFile
from factory.django import DjangoModelFactory
from io import BytesIO
from PIL import Image as PilImage
from random import randint

class 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}')