我想模拟一个类的方法并使用wraps
,以便实际调用它,但我可以检查传递给它的参数。我在几个地方看到过(例如here),通常的做法如下(适应我的观点):
from unittest import TestCase
from unittest.mock import patch
class Potato(object):
def foo(self, n):
return self.bar(n)
def bar(self, n):
return n + 2
class PotatoTest(TestCase):
spud = Potato()
@patch.object(Potato, 'foo', wraps=spud.foo)
def test_something(self, mock):
forty_two = self.spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
但是,这会实例化类Potato
,以便将模拟绑定到实例方法spud.foo
。
我需要的是在 foo
的所有实例中模拟方法Potato
,并将它们包装在原始方法周围。即,我需要以下内容:
from unittest import TestCase
from unittest.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', wraps=Potato.foo)
def test_something(self, mock):
self.spud = Potato()
forty_two = self.spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
这当然不起作用。我收到错误:
TypeError: foo() missing 1 required positional argument: 'self'
但是如果没有使用wraps
,那么它的工作原理是错误的,因此问题不在于模拟本身,而在于它调用包装函数的方式。例如,这有效(但当然我必须"假"返回的值,因为现在Potato.foo
从未实际运行):
from unittest import TestCase
from unittest.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', return_value=42)#, wraps=Potato.foo)
def test_something(self, mock):
self.spud = Potato()
forty_two = self.spud.foo(n=40)
mock.assert_called_once_with(n=40)
self.assertEqual(forty_two, 42)
这样可行,但它不运行我需要运行的原始函数,因为返回值在别处使用(我不能在测试中伪造它)。
可以吗?
注意我需要的实际原因是我正在使用webtest测试rest api。从测试中,我对一些路径执行一些wsgi请求,我的框架实例化一些类并使用它们的方法来完成请求。我想捕获发送到这些方法的参数,以便在我的测试中对它们进行一些asserts
。
答案 0 :(得分:1)
简而言之,您不能仅使用Mock
实例来执行此操作。
patch.object
为指定实例(土豆)创建Mock
,即在调用它时用单个Mock替换Potato.foo
。因此,由于无法在任何实例创建之前创建模拟,因此无法将实例传递给Mock
。据我所知,在运行时将实例信息传递到Mock
也是非常困难的。
说明:
from unittest.mock import MagicMock
class MyMock(MagicMock):
def __init__(self, *a, **kw):
super(MyMock, self).__init__(*a, **kw)
print('Created Mock instance a={}, kw={}'.format(a,kw))
with patch.object(Potato, 'foo', new_callable=MyMock, wrap=Potato.foo):
print('no instances created')
spud = Potato()
print('instance created')
输出为:
Created Mock instance a=(), kw={'name': 'foo', 'wrap': <function Potato.foo at 0x7f5d9bfddea0>}
no instances created
instance created
我建议用猴子修补类,以将Mock
添加到正确的位置。
from unittest.mock import MagicMock
class PotatoTest(TestCase):
def test_something(self):
old_foo = Potato.foo
try:
mock = MagicMock(wraps=Potato.foo, return_value=42)
Potato.foo = lambda *a,**kw: mock(*a, **kw)
self.spud = Potato()
forty_two = self.spud.foo(n=40)
mock.assert_called_once_with(self.spud, n=40) # Now needs self instance
self.assertEqual(forty_two, 42)
finally:
Potato.foo = old_foo
请注意,在使用实例调用函数时,使用called_with
存在问题。
答案 1 :(得分:0)
您控制Potato
实例的创建,还是至少在创建这些实例后有权访问这些实例?您应该这样做,否则您将无法检查特定的arg列表。
如果是这样,您可以使用
spud = dig_out_a_potato()
with mock.patch.object(spud, "foo", wraps=spud.foo) as mock_spud:
# do your thing.
mock_spud.assert_called...