是否有一种干净的方法来修补对象,以便在测试用例中获得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
一起破解这个,但我希望有更好的方法在所有函数,类方法,静态方法,未绑定方法等上以相同的方式工作。
答案 0 :(得分:29)
与您的类似解决方案,但使用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)
包装:要包装的模拟对象的项目。如果换行不是无。那么 调用Mock会将调用传递给包装对象 (返回实际结果)。模拟的属性访问将返回 一个Mock对象,它包装了包装的相应属性 对象(因此尝试访问不存在的属性将会 引发一个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)
答案 1 :(得分:7)
这个答案解决了用户Quuxplusone的赏金中提到的额外要求:
对我的用例来说重要的是它与
@patch.mock
一起使用,即它不需要我在构造Potato
实例之间插入任何代码({{1}在这个例子中)和我对spud
的调用。我需要从一开始就使用模拟spud.foo
方法创建spud
,因为我无法控制创建foo
的位置。
使用装饰器可以毫不费力地实现上述用例:
spud
如果替换的方法接受在测试中修改的可变参数,您可能希望初始化CopyingMock
* 来代替spy_decorator中的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()
。
* 这是我从docs发布的关于PyPI的copyingmock lib
的食谱