78

I'm having some trouble dealing with the nested tuple which Mock.call_args_list returns.

def test_foo(self):
    def foo(fn):
        fn('PASS and some other stuff')

    f = Mock()
    foo(f)
    foo(f)
    foo(f)

    for call in f.call_args_list:
        for args in call:
            for arg in args:
                self.assertTrue(arg.startswith('PASS'))

I would like to know if there is a better way to unpack that call_args_list on the mock object in order to make my assertion. This loop works, but it feels like there must be a more straight forward way.

nackjicholson
  • 4,557
  • 4
  • 37
  • 35
  • I think this really depends on what assertion you're trying to make. Are you trying to check only positional arguments? Are you trying to check positional and keyword arguments? Are you trying to check the keywords themselves or the values passed via keyword arguments? e.g. if you only want to check that the first positional argument starts with `'PASS'`, then `self.assertTrue(call[0][0].startswith('Pass'))` should do the trick without the inner 2 loops. – mgilson Sep 23 '16 at 21:10

2 Answers2

111

I think that many of the difficulties here are wrapped up in the treatment of the "call" object. It can be thought of as a tuple with 2 members (args, kwargs) and so it's frequently nice to unpack it:

args, kwargs = call

Once it's unpacked, then you can make your assertions separately for args and kwargs (since one is a tuple and the other a dict)

def test_foo(self):
    def foo(fn):
        fn('PASS and some other stuff')

    f = Mock()
    foo(f)
    foo(f)
    foo(f)

    for call in f.call_args_list:
        args, kwargs = call
        self.assertTrue(all(a.startswith('PASS') for a in args))

Note that sometimes the terseness isn't helpful (e.g. if there is an error):

for call in f.call_args_list:
    args, kwargs = call
    for a in args:
        self.assertTrue(a.startswith('PASS'), msg="%s doesn't start with PASS" % a)
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • @wim -- Yeah. I've seen your way used _many_ times, but it always felt like it was too much trying to be a record/playback type of framework. There _are_ record/playback frameworks for python unit-tests, but I never really considered `unittest.mock` to be one of them :-) – mgilson Sep 23 '16 at 21:52
  • 6
    Oh, man `args, kwargs = call` is the clue to all the `call_args_list` hacks. Thank you! – I159 Jan 17 '20 at 11:58
  • If you know there will only be one positional argument (`message` in this case), you can unpack that in one step: `(message, ), kwargs = call` – Fush Jun 23 '21 at 07:25
13

A nicer way might be to build up the expected calls your self then use a direct assertion:

>>> from mock import call, Mock
>>> f = Mock()
>>> f('first call')
<Mock name='mock()' id='31270416'>
>>> f('second call')
<Mock name='mock()' id='31270416'>
>>> expected_calls = [call(s + ' call') for s in ('first', 'second')]
>>> f.assert_has_calls(expected_calls)

Note that the calls must be sequential, if you don't want that then override the any_order kwarg to the assertion.

Also note that it's permitted for there to be extra calls before or after the specified calls. If you don't want that, you'll need to add another assertion:

>>> assert f.call_count == len(expected_calls)

Addressing the comment of mgilson, here's an example of creating a dummy object that you can use for wildcard equality comparisons:

>>> class AnySuffix(object):
...     def __eq__(self, other):
...         try:
...             return other.startswith('PASS')
...         except Exception:
...             return False
...        
>>> f = Mock()
>>> f('PASS and some other stuff')
<Mock name='mock()' id='28717456'>
>>> f('PASS more stuff')
<Mock name='mock()' id='28717456'>
>>> f("PASS blah blah don't care")
<Mock name='mock()' id='28717456'>
>>> expected_calls = [call(AnySuffix())]*3
>>> f.assert_has_calls(expected_calls)

And an example of the failure mode:

>>> Mock().assert_has_calls(expected_calls)
AssertionError: Calls not found.
Expected: [call(<__main__.AnySuffix object at 0x1f6d750>),
 call(<__main__.AnySuffix object at 0x1f6d750>),
 call(<__main__.AnySuffix object at 0x1f6d750>)]
Actual: []
wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    This only works if OP actually knows what each call should look like _exactly_. Based on the question, it looks like OP only knows what the first part of each parameter is supposed to look like (which is weird ... but hey...) – mgilson Sep 23 '16 at 21:16
  • Hmm, `call` is just a tuple so it can still be modified to work. I will edit to show how ... – wim Sep 23 '16 at 21:17
  • It's more that I don't want my test to care what comes after the first part of the parameter, because what comes after is really verbose and changes a lot, so having the tests assert on it becomes really tedious. – nackjicholson Sep 23 '16 at 21:20
  • 1
    I added an example to show how to fuzzy match the call. However, as a cautionary tale, if you can't exactly predict the outcome of a call in tests it's often a warning sign that there's something which is not mocked that should have been .. – wim Sep 23 '16 at 21:33