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


76

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

requests.post = Mock()

该调用涉及多个参数:URL,有效负载,一些身份验证内容等。我想断言requests.post使用特定的URL进行调用,但我不在乎其他参数。当我尝试这个:

requests.post.assert_called_with(requests_arguments)

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

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

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


与问题无关,但是如果您要requests-mock模拟REST调用,则该模块可能也很有趣。
TheHowlingHoaschd

Answers:


55

据我所知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,否则比较将无法进行1。但是,您仍然必须提供所有参数。如果您有很多参数,则可以使用元组拆包来缩短代码:

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])

但是,这不允许使用例如anint或a作为参数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内置的...


3
我对此使用了一种变体,但是在较新版本的模拟中,它们使用!=作为比较(至少对于kwargs而言),因此您也需要重写def __neq__(self, other): return False
安德鲁·贝克尔

2
现在已将其内置到框架中(无类型检查),并给出了另一个答案:stackoverflow.com/a/27152023/452274
Matt

模拟提供了一种方法。见答案来自@ k0nG
马尔辛-

@Marcin不,不是。ANY不提供类型检查。
Bakuriu

230

您也可以使用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)

1
我也使用此解决方案,但是它不提供类型检查。
jackdbernier

23
实际上,这应该是可以接受的答案,因为它可以很好地工作。
Jernej Jerin

1
这绝对是正确的答案。可以正常工作并且很干净。
Ori

27
@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'))

请参阅:按元组调用


3

如果要传递的参数太多,并且仅要检查其中一个参数,则{'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }可能会很笨拙。


我采用以下方法来实现此目的:

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

简单来说,这将返回一个元组,其中包含所有位置参数和字典,并将所有命名参数传递给函数调用,因此现在您可以检查任何所需的内容。


此外,如果您想检查几个字段的数据类型

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'
)

0

您可以使用Mock.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)

上面的代码将输出odered参数和关键字参数,如下所示:

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

您可以这样声明:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.