Тесты 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}')