unittest.mock
对象有一个 assert_not_called
方法可用,但我正在寻找的是 assert_not_called_with
。有这样的事吗?我在 Google 上查看,没有看到任何内容,当我尝试仅使用 mock_function.assert_not_called_with(...)
时,它引发了 AttributeError
,这意味着该名称的函数不存在。
我目前的解决方案:
with self.assertRaises(AssertionError):
mock_function.assert_called_with(arguments_I_want_to_test)
这可行,但如果我想进行多个此类调用,则会使代码变得混乱。
相关:
您可以自行向
assert_not_called_with
添加 unittest.mock.Mock
方法:
from unittest.mock import Mock
def assert_not_called_with(self, *args, **kwargs):
try:
self.assert_called_with(*args, **kwargs)
except AssertionError:
return
raise AssertionError('Expected %s to not have been called.' % self._format_mock_call_signature(args, kwargs))
Mock.assert_not_called_with = assert_not_called_with
这样:
m = Mock()
m.assert_not_called_with(1, 2, a=3)
m(3, 4, b=5)
m.assert_not_called_with(3, 4, b=5)
输出:
AssertionError: Expected mock(3, 4, b=5) to not have been called.
另一个使用模拟调用历史记录的解决方案:
from unittest.mock import call
assert call(arguments_I_want_to_test) not in mock_function.mock_calls
使用 Pytest,我断言调用了“AssertionError”:
import pytest
from unittest.mock import Mock
def test_something():
something.foo = Mock()
# Test that something.foo(bar) is not called.
with pytest.raises(AssertionError):
something.foo.assert_called_with(bar)
Andrey Semakin 的答案应该被接受。接受的答案是有问题的,因为
assert_called_with
仅检查模拟函数的 last 调用。假设您有 6 个调用:您可能想查明具有给定参数的给定调用是否是这 6 个调用中的任何一个(或多个)进行的。接受的答案不适用于这种情况,但 Andrey Semakin 的答案适用。
assert_any_call
并捕获断言异常来完成他的解决方案的功能。
Andrey 的答案可以变得更有用:假设您不想规定所有
args
或所有 kwargs
?使用他的解决方案(以及 assert_any_call
异常捕获),您必须精确规定所有 args
和所有 kwargs
,否则匹配不会发生。如果您正在寻找匹配的缺席,那么在测试环境中必须规定所有参数和所有 kwargs 通常可能会过于苛刻。
情况 1:忽略参数
这可以通过使用虚拟类轻松完成:
class AnyArg(object):
def __eq__(self, b):
return True
args = (AnyArg(),)
kwargs = {'command': 'put'}
assert mock.call(*args, **kwargs) in mock_process.mock_calls
注意,如果您的
args
是真实参数和 AnyArg
的混合,我不确定会发生什么,也没有尝试找出答案。实际上,这个解决方案只是为了忽略所有参数。但这仍然是有问题的:调用中提供的任何其他kwargs
并且匹配失败(或者如果您检查缺席情况,您会得到可能是假阴性的结果)。
情况 2:忽略 kwargs(或 args 和 kwargs)
这个就比较困难了。您不能伪造在另一个键值对列表中找到或未找到键值对。我认为这尽可能紧凑:
def is_match_found(mock_func, required_args=(), required_kwargs={}):
for args_list in mock_func.call_args_list:
for required_arg in required_args:
if required_arg not in args_list.args:
break # go on to next call
else:
for required_key in required_kwargs:
if required_key not in args_list.kwargs:
break # go on to next call
else:
if required_kwargs[required_key] != args_list.kwargs[required_key]:
# values differ
break # go on to next call
else:
return True
return False
使用示例:
# stipulating some args, disregarding kwargs
assert is_match_found(mock_process, ('some parameter', 33,))
# stipulating some args and some kwargs, "permissively"
assert not is_match_found(mock_process, ('some parameter', 33,), {'command': 'put',})
# stipulating some args and some kwargs, disregarding *values* of named params,
# but ensuring that those named parameters are present
assert not is_match_found(mock_process, ('some parameter', 33,), {'command': AnyArg(),})
我使用“permissively”这个词,这意味着对此函数的任何调用,如所写的,完全忽略任何不匹配的额外参数或 kwargs。显然,可以对上述内容进行无限的改进(特别是 args
的
order可能是一个要求),但出于测试目的,我认为这是一个非常有用的工具。
尊重所需参数的顺序
这有望解决问题(并希望通过重复的参数给出正确的结果):
def is_match_found(mock_func, required_args=(), required_kwargs={}, args_must_be_leading=True):
for args_list in mock_func.call_args_list:
call_args = args_list.args
for required_arg in required_args:
if required_arg not in call_args:
break # go on to next call
index_in_call_args = call_args.index(required_arg)
if args_must_be_leading and index_in_call_args != 0:
break # go on to next call
call_args = call_args[index_in_call_args + 1:]
[snip...]
不幸的是,事实证明你不能分配给
args_list.args
,所以我最初认为我们将被迫制作该列表的(可能昂贵的深层)副本。然而,似乎只需将 call_args
分配给 args_list.args
就可以绕过这个问题。
当然,这远非完美。特别是,在上面的第一个示例中,如果
args_must_be_leading == False
与 args
(None, 12, 'some parameter', 3, 3, 33, 'entropy')
的调用将有资格作为“通过”。我添加该参数是为了在宽容和限制之间实现稍微更合理的平衡。
另一种选择:
self.assertNotIn(argument_you_want_to_test, mock_function.call_args.kwargs)