断言一个方法是用几个参数调用的

时间:2014-02-06 18:55:47

标签: python unit-testing

我正在使用requests.post库嘲笑Mock的电话:

requests.post = Mock()

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

requests.post.assert_called_with(requests_arguments)

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

有没有办法检查函数调用中某处是否使用了单个参数而不必传递其他参数?

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

6 个答案:

答案 0 :(得分:127)

您还可以使用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 :(得分:41)

据我所知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 。但是,您仍然必须提供所有参数。如果您有许多参数,可以使用tuple-unpacking缩短代码:

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

然而,这不允许参数,例如,intstr。允许多个参数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也是内置的......

答案 2 :(得分:16)

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

请参阅:calls-as-tuples

答案 3 :(得分:0)

如果传递的参数太多,并且仅要检查其中一个参数,则执行{'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'
)

答案 4 :(得分:0)

您可以使用Mock.call_args来收集调用方法的参数。如果您的模拟方法已被调用,它将以有序参数和关键字参数的元组的形式返回调用您的方法的参数。

{% load socialaccount %}

以上代码将按如下所示输出odered参数和关键字参数:

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

答案 5 :(得分:-2)

你可以使用:assert_any_call(args) https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_any_call

requests.post.assert_any_call(requests_arguments)