如何断言模拟对象未使用特定参数调用?

问题描述 投票:0回答:5

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)

这可行,但如果我想进行多个此类调用,则会使代码变得混乱。

相关:

python python-unittest python-unittest.mock
5个回答
31
投票

您可以自行向

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.

15
投票

另一个使用模拟调用历史记录的解决方案:

from unittest.mock import call

assert call(arguments_I_want_to_test) not in mock_function.mock_calls

5
投票

使用 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)

2
投票

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')
的调用将有资格作为“通过”。我添加该参数是为了在宽容和限制之间实现稍微更合理的平衡。


0
投票

另一种选择:

self.assertNotIn(argument_you_want_to_test, mock_function.call_args.kwargs)
© www.soinside.com 2019 - 2024. All rights reserved.