简体   繁体   中英

Python Unittest Mock assert_has_calls returning calls to other mocks

Here's a toy example that illustrates my problem.

Code

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

Tests

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

Results

The first test passes.

The second test fails, with the following exception:

Failure
Traceback (most recent call last):
  ...
AssertionError: Calls not found.
Expected: [call(), call()]
Actual: [call(), call().do_a_thing(), call(), call().do_a_thing()]

This feels like really strange behaviour - I'm asserting about calls to the get_bar method on the bar_supplier mock, but then the calls list includes calls to a different mock which is returned by the get_bar method.

I'm sure this is a misunderstanding rather than a bug, but how can I best avoid getting those .do_a_thing() calls in my list of calls?

It is because the same mock for .get_bar() always subsequently calls .do_a_thing() which as 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.

Wherein mock_calls includes not just the calls to itself:

mock_calls

mock_calls records all calls to the mock object , its methods , magic methods and return value mocks .

Solution 1

You can use the any_order=True setting for assert_has_calls which as documented:

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.

So change:

bar_supplier.get_bar.assert_has_calls([
    call(),
    call()
])

To:

bar_supplier.get_bar.assert_has_calls([
    call(),
    call()
],
any_order=True)

Solution 2

An alternative is checking call_args_list instead:

assert bar_supplier.get_bar.call_args_list == [
    call(),
    call()
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM