#python #python-unittest #python-mock
#python #python-unittest #python-макет
Вопрос:
в Python 3.6 я использую unittest.mock.patch
для исправления функции, подобной этой:
class SampleTest(TestCase):
@mock.patch('some_module.f')
def test_f(self, mocked_f):
f()
mocked_f.assert_called()
Это передает mock.MagicMock()
как mocked_f
, и все работает нормально. Однако, когда я хочу использовать пользовательский объект mock вместо аргумента по умолчанию mock.MagicMock()
using new
, декоратор patch не передает объект mocked в test_f
метод. Запуск этого кода вызовет TypeError
:
class SampleTest(TestCase):
@mock.patch('some_module.f', new=lambda: 8)
def test_f(self, mocked_f):
f()
mocked_f.assert_called()
TypeError: test_f() missing 1 required positional argument: 'mocked_f'
Мой вопрос: почему это происходит?
Ответ №1:
Я думаю, что, вероятно, правильно, что причина, по которой имитируемый объект не передается оформленной функции, когда new
указано, заключается в том, что у вас обычно уже есть ссылка на этот объект, и поэтому вам не нужно передавать его в оформленную функцию.
Обратите внимание, однако, что если вы используете new_callable
вместо new
, то имитируемый объект передается оформленной функции. Это имеет смысл, поскольку обычно у вас не будет ссылки на объект, возвращаемый из вызываемого объекта.
итак, вы можете сделать что-то вроде следующего:
def my_custom_mock_factory():
my_mock = mock.Mock()
# customisations to my_mock
return my_mock
class SampleTest(TestCase):
@mock.patch('some_module.f', new_callable=my_custom_mock_factory)
def test_f(self, mocked_f):
f()
mocked_f.assert_called()
Ответ №2:
Из документации (выделено мной):
Если
patch()
используется в качестве декоратора и new опущен, созданный макет передается в качестве дополнительного аргумента оформленной функции.
При new
явном использовании декоратор не передает mocked объект в качестве параметра (предположительно, потому что он ожидает, что у вас уже есть ссылка, которую вы могли бы использовать без необходимости аргумента).
В этом случае обходным путем было бы настроить макет после его передачи в ваш тест:
class SampleTest(TestCase):
@mock.patch('tests.f')
def test_f(self, mocked_f):
mocked_f.return_value = 8
# or
# mocked_f.side_effect = lambda: 8
f()
mocked_f.assert_called()
Комментарии:
1. Спасибо за ответ. Я обратил внимание на документацию, о которой вы упоминали ранее, но я не убежден в этом предположении. Ваше решение идеально подходит для моего примера, но бывают ситуации, когда
return_value
orside_effect
не предоставляет удобного обходного пути. Это блог, который я читал, когда столкнулся с проблемой: blog.miguelgrinberg.com/post/unit-testing-asyncio-code Я мог бы обновить свой вопрос примером в блоге, но он слишком длинный.