断言某个方法是用多个参数中的一个参数调用的

我正在模拟使用 Mock库调用 requests.post:

requests.post = Mock()

该调用涉及多个参数: URL、有效负载、一些认证内容等。我想声明 requests.post是用特定的 URL 调用的,但是我不关心其他参数。当我尝试这个:

requests.post.assert_called_with(requests_arguments)

测试失败,因为它期望只使用该参数调用它。

有没有什么方法可以检查在函数调用的某个地方是否使用了单个参数,而不必传入其他参数?

或者,更好的是,有没有一种方法可以断言一个特定的 URL,然后为其他参数抽象数据类型(例如,数据应该是一个字典,auth 应该是 HTTPBasicAuth 的一个实例,等等) ?

114282 次浏览

据我所知,Mock并不提供一种通过 assert_called_with实现你想要的东西的方法。您可以访问 call_argscall_args_list成员并手动执行断言。

然而,这是一个简单(和肮脏)的方式实现 差不多你想要的。你必须实现一个类,它的 __eq__方法总是返回 True:

def Any(cls):
class Any(cls):
def __eq__(self, other):
return True
return Any()

使用它作为:

In [14]: caller = mock.Mock(return_value=None)




In [15]: caller(1,2,3, arg=True)


In [16]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=True)


In [17]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-17-c604faa06bd0> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)


/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724         if self.call_args != (args, kwargs):
725             msg = self._format_mock_failure_message(args, kwargs)
--> 726             raise AssertionError(msg)
727
728


AssertionError: Expected call: mock(0, 0, 0, arg=False)
Actual call: mock(1, 2, 3, arg=True)

正如你所看到的,它只检查 arg。您必须创建 int的子类,否则比较不会起作用。然而,你仍然必须提供所有的论据。如果有很多参数,可以使用 tuple-unpack 来缩短代码:

In [18]: caller(1,2,3, arg=True)


In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)

除此之外,我想不出一种方法来避免传递所有参数到 assert_called_with,并按照您的意愿工作。


可以扩展上述解决方案来检查其他参数的类型,例如:

In [21]: def Any(cls):
...:     class Any(cls):
...:         def __eq__(self, other):
...:             return isinstance(other, cls)
...:     return Any()


In [22]: caller(1, 2.0, "string", {1:1}, [1,2,3])


In [23]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(list))


In [24]: caller(1, 2.0, "string", {1:1}, [1,2,3])


In [25]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-25-f607a20dd665> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))


/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724         if self.call_args != (args, kwargs):
725             msg = self._format_mock_failure_message(args, kwargs)
--> 726             raise AssertionError(msg)
727
728


AssertionError: Expected call: mock(0, 0.0, '', {}, ())
Actual call: mock(1, 2.0, 'string', {1: 1}, [1, 2, 3])

然而,这不允许参数可以是,例如,一个 int或一个 str。允许 Any使用多个参数并使用多重继承不会有帮助。我们可以用 abc.ABCMeta解决这个问题

def Any(*cls):
class Any(metaclass=abc.ABCMeta):
def __eq__(self, other):
return isinstance(other, cls)
for c in cls:
Any.register(c)
return Any()

例如:

In [41]: caller(1, "ciao")


In [42]: caller.assert_called_with(Any(int, str), Any(int, str))


In [43]: caller("Hello, World!", 2)


In [44]: caller.assert_called_with(Any(int, str), Any(int, str))

1 我使用了函数名 Any,因为它在代码中是“用作类”的。而且 any是一个内置的..。

您还可以使用 ANY助手始终匹配您不知道或没有检查的参数。

更多关于任何助手的资料: Https://docs.python.org/3/library/unittest.mock.html#any

例如,你可以将参数‘ session’与任何类似的东西进行匹配:

from unittest.mock import ANY
requests_arguments = {'slug': 'foo', 'session': ANY}
requests.post.assert_called_with(requests_arguments)
@mock.patch.object(module, 'ClassName')
def test_something(self, mocked):
do_some_thing()
args, kwargs = mocked.call_args
self.assertEqual(expected_url, kwargs.get('url'))

见: 叫做元组

可以使用: asser_ any _ call (args) Https://docs.python.org/3/library/unittest.mock.html#unittest.mock 调用

Asser_ any _ call (requests.post.asser_ any _ call)

TLDR:

args, kwargs = requests.post.call_args_list[-1]
self.assertTrue('slug' in kwargs, '`slug` not passed to requests.post')

简单来说,我们可以访问包含所有位置参数的元组和包含传递给函数的所有命名参数的字典,因此现在可以检查任何内容。



我发现这种方法比其他流行的答案要干净得多,因为: < br/> 如果有太多的参数被传递,并且只有一个参数需要检查,那么执行类似于 {'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }的操作可能会很笨拙。


此外,如果您想检查一些字段的数据类型:

args, kwargs = requests.post.call_args_list[0]
self.assertTrue(isinstance(kwargs['data'], dict))

另外,如果你传递参数(而不是关键字参数) ,你可以像这样通过 args访问它们:

self.assertEqual(
len(args), 1,
'post called with different number of arguments than expected'
)

可以使用 Call _ args收集调用方法的参数。如果调用了模拟方法,它将以有序参数元组和关键字参数的形式返回调用方法时使用的参数。

class A(object):
def a_method(self, a, b,c=None):
print("Method A Called")


def main_method():
# Main method instantiates a class A and call its method
a = A()
a.a_method("vikalp", "veer",c= "Test")


# Test main method :  We patch instantiation of A.
with patch(__name__ + '.A') as m:
ret = m.return_value
ret.a_method = Mock()
res = main_method()
args, kwargs = ret.a_method.call_args
print(args)
print(kwargs)

以上代码将输出有序参数和关键字参数如下:

('vikalp', 'veer')
{'c': 'Test'}

你可以这样断言:

assert args[0] == "vikalp"
assert kwargs['c'] == "Test"

下面的一行程序将声明 requests_arguments至少出现在对 requests.post 调用的位置参数列表中一次

assert any(map(lambda args: requests_arguments in args[0], requests.post.call_args_list))

完整的工作范例:

import unittest.mock


post = unittest.mock.Mock()
post('foo')
post('bar')
assert any(map(lambda args: 'foo' in args[0], requests.post.call_args_list))