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