Python 单元测试模拟 assert_has_calls 返回对其他模拟的调用
Python Unittest Mock assert_has_calls returning calls to other mocks
这里有一个玩具示例可以说明我的问题。
代码
class Bar:
def do_a_thing(self):
print('doing a thing')
class BarSupplier:
def get_bar(self) -> Bar:
return Bar()
class Foo:
def __init__(self, bar_supplier: BarSupplier):
self.bar_supplier = bar_supplier
def do_foo(self):
self.bar_supplier.get_bar().do_a_thing()
测试
from unittest import TestCase
from unittest.mock import MagicMock, call
from calls_example import Foo
class TestCallsExample(TestCase):
def test_once(self):
bar_supplier = MagicMock()
bar_supplier.get_bar.return_value = MagicMock()
foo = Foo(bar_supplier)
foo.do_foo()
bar_supplier.get_bar.assert_has_calls([
call(),
])
def test_twice(self):
bar_supplier = MagicMock()
bar_supplier.get_bar.return_value = MagicMock()
foo = Foo(bar_supplier)
foo.do_foo()
foo.do_foo()
bar_supplier.get_bar.assert_has_calls([
call(),
call()
])
结果
第一个测试通过。
第二次测试失败,异常如下:
Failure
Traceback (most recent call last):
...
AssertionError: Calls not found.
Expected: [call(), call()]
Actual: [call(), call().do_a_thing(), call(), call().do_a_thing()]
这感觉真的很奇怪 - 我断言在 bar_supplier
模拟上调用 get_bar
方法,但是调用列表包括对另一个模拟的调用,该模拟由get_bar
方法。
我确定这是误会而不是错误,但如何才能最好地避免在我的通话列表中收到那些 .do_a_thing()
来电?
这是因为 .get_bar()
的同一个 mock 总是随后调用 .do_a_thing()
,即 documented:
assert_has_calls(calls, any_order=False)
assert the mock has been called with the specified calls. The mock_calls
list is checked for the calls.
其中 mock_calls 不仅包括对自身的调用:
mock_calls
mock_calls records all calls to the mock object, its methods, magic methods and return value mocks.
解决方案 1
您可以使用 assert_has_calls 的 any_order=True
设置,如文档所述:
If any_order
is false then the calls must be sequential. There can be extra calls before or after the specified calls.
If any_order
is true then the calls can be in any order, but they must all appear in mock_calls.
所以改变:
bar_supplier.get_bar.assert_has_calls([
call(),
call()
])
收件人:
bar_supplier.get_bar.assert_has_calls([
call(),
call()
],
any_order=True)
解决方案 2
替代方法是检查 call_args_list:
assert bar_supplier.get_bar.call_args_list == [
call(),
call()
]
这里有一个玩具示例可以说明我的问题。
代码
class Bar:
def do_a_thing(self):
print('doing a thing')
class BarSupplier:
def get_bar(self) -> Bar:
return Bar()
class Foo:
def __init__(self, bar_supplier: BarSupplier):
self.bar_supplier = bar_supplier
def do_foo(self):
self.bar_supplier.get_bar().do_a_thing()
测试
from unittest import TestCase
from unittest.mock import MagicMock, call
from calls_example import Foo
class TestCallsExample(TestCase):
def test_once(self):
bar_supplier = MagicMock()
bar_supplier.get_bar.return_value = MagicMock()
foo = Foo(bar_supplier)
foo.do_foo()
bar_supplier.get_bar.assert_has_calls([
call(),
])
def test_twice(self):
bar_supplier = MagicMock()
bar_supplier.get_bar.return_value = MagicMock()
foo = Foo(bar_supplier)
foo.do_foo()
foo.do_foo()
bar_supplier.get_bar.assert_has_calls([
call(),
call()
])
结果
第一个测试通过。
第二次测试失败,异常如下:
Failure
Traceback (most recent call last):
...
AssertionError: Calls not found.
Expected: [call(), call()]
Actual: [call(), call().do_a_thing(), call(), call().do_a_thing()]
这感觉真的很奇怪 - 我断言在 bar_supplier
模拟上调用 get_bar
方法,但是调用列表包括对另一个模拟的调用,该模拟由get_bar
方法。
我确定这是误会而不是错误,但如何才能最好地避免在我的通话列表中收到那些 .do_a_thing()
来电?
这是因为 .get_bar()
的同一个 mock 总是随后调用 .do_a_thing()
,即 documented:
assert_has_calls(calls, any_order=False)
assert the mock has been called with the specified calls. The
mock_calls
list is checked for the calls.
其中 mock_calls 不仅包括对自身的调用:
mock_calls
mock_calls records all calls to the mock object, its methods, magic methods and return value mocks.
解决方案 1
您可以使用 assert_has_calls 的 any_order=True
设置,如文档所述:
If
any_order
is false then the calls must be sequential. There can be extra calls before or after the specified calls.If
any_order
is true then the calls can be in any order, but they must all appear in mock_calls.
所以改变:
bar_supplier.get_bar.assert_has_calls([
call(),
call()
])
收件人:
bar_supplier.get_bar.assert_has_calls([
call(),
call()
],
any_order=True)
解决方案 2
替代方法是检查 call_args_list:
assert bar_supplier.get_bar.call_args_list == [
call(),
call()
]