Python mock - исправление метода без препятствия реализации
Есть ли чистый способ для исправления объекта, чтобы вы получили помощники assert_call*
в вашем тестовом примере, без фактического удаления действия?
Например, как я могу изменить строку @patch
, чтобы получить следующую тестовую передачу:
from unittest import TestCase
from mock import patch
class Potato(object):
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(TestCase):
@patch.object(Potato, 'foo')
def test_something(self, mock):
spud = Potato()
forty_two = spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Я мог бы взломать это вместе, используя side_effect
, но я надеялся, что будет более удобный способ, который работает одинаково со всеми функциями, методами класса, staticmethods, несвязанными методами и т.д.
Ответы
Ответ 1
Аналогичное решение с вашим, но с помощью wraps
:
def test_something(self):
spud = Potato()
with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
forty_two = spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Согласно документации:
wraps: элемент для обманного объекта. Если обертки не являются None, тогда вызов Mock передаст вызов через обернутый объект (возврат реального результата). Атрибут доступа к макету будет возвращен объект Mock, который обертывает соответствующий атрибут завернутого object (поэтому попытка доступа к атрибуту, который не существует, будет поднять AttributeError).
class Potato(object):
def spam(self, n):
return self.foo(n=n)
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(TestCase):
def test_something(self):
spud = Potato()
with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
forty_two = spud.spam(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
Ответ 2
В этом ответе укажите дополнительное требование, указанное в награде от пользователя Quuxplusone:
Важным моментом для моего использования является то, что он работает с @patch.mock
, т.е. что мне не требуется вставлять какой-либо код между моим конструированием экземпляра Potato
(spud
в этом примере) и мое призвание spud.foo
. Мне нужно spud
создать с помощью mocked-out foo
метода из get-go, потому что я не контролирую место создания spud
.
Вариант использования, описанный выше, может быть достигнут без особых проблем с помощью декоратора:
import unittest
import unittest.mock # Python 3
def spy_decorator(method_to_decorate):
mock = unittest.mock.MagicMock()
def wrapper(self, *args, **kwargs):
mock(*args, **kwargs)
return method_to_decorate(self, *args, **kwargs)
wrapper.mock = mock
return wrapper
def spam(n=42):
spud = Potato()
return spud.foo(n=n)
class Potato(object):
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(unittest.TestCase):
def test_something(self):
foo = spy_decorator(Potato.foo)
with unittest.mock.patch.object(Potato, 'foo', foo):
forty_two = spam(n=40)
foo.mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
if __name__ == '__main__':
unittest.main()
Если замененный метод принимает изменяемые аргументы, которые изменяются под тестом, вы можете инициализировать CopyingMock
вместо MagicMock
внутри spy_decorator.