Python单元测试Mock:assert_has_calls返回对其他mock的调用。

3

这里是一个玩具示例,用来说明我的问题。

代码

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() 调用?

请参见相关链接 https://bugs.python.org/issue43371 - Roy Smith
1个回答

2
由于相同的.get_bar()模拟总是随后调用.do_a_thing(),如文档所述:

assert_has_calls(calls, any_order=False)

断言mock已使用指定的调用进行调用。检查mock_calls列表以获取调用。

其中mock_calls不仅包括对自身的调用:

mock_calls

mock_calls记录所有对mock对象、其方法、魔术方法和返回值mock的调用。

解决方案1

您可以使用assert_has_callsany_order=True设置,如文档所述:

如果any_order为false,则必须按顺序进行调用。在指定的调用之前或之后可能会有额外的调用。

如果any_order为true,则调用可以是任何顺序,但它们必须全部出现在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()
]

我不想做任何排序,因为在真实的生产情况下(而不是这个玩具示例),我希望调用按特定顺序进行。此外,这不是同一个模拟对象。我要求调用bar_supplier模拟对象的get_bar方法,并设置返回一个不同的MagicMock对象。 - Paul Davies
call_args_list 这个提示很有帮助,谢谢。可惜没有直接的断言可以使用。 - Paul Davies
@PaulDavies 我忘记了assert命令,请确保添加它 :) - Niel Godfrey Pablo Ponciano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接