Python — почему декоратор фиктивного исправления не передает имитируемый объект тестовой функции, когда аргумент `new` не задан по УМОЛЧАНИЮ

#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 or side_effect не предоставляет удобного обходного пути. Это блог, который я читал, когда столкнулся с проблемой: blog.miguelgrinberg.com/post/unit-testing-asyncio-code Я мог бы обновить свой вопрос примером в блоге, но он слишком длинный.