12

Consider the following files:

holy_hand_grenade.py

def count(one, two, five='three'):
    print('boom')

test_holy_hand_grenade.py

from unittest import mock
import holy_hand_grenade

def test_hand_grenade():
    mock_count = mock.patch("holy_hand_grenade.count", autospec=True)
    with mock_count as fake_count:
        fake_count(1, 2, five=5)

        # According to https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_args
        # this should work
        assert fake_count.call_args.kwargs['five'] == 5

According to the docs, call_args should be:

This is either None (if the mock hasn’t been called), or the arguments that the mock was last called with. This will be in the form of a tuple: the first member, which can also be accessed through the args property, is any ordered arguments the mock was called with (or an empty tuple) and the second member, which can also be accessed through the kwargs property, is any keyword arguments (or an empty dictionary).

(emphasis mine)

But this blows up in my face, with TypeError: tuple indices must be integers or slices, not str

Um. No?

The thing I really don't understand, is that if this is a call object, which it is, because

assert isinstance(fake_count.call_args, (mock._Call,))

passes, it's supposed to have kwargs and args. And it... well, it sort of does. But they appear to not actually be the correct thing:

assert isinstance(fake_count.call_args.kwargs, (mock._Call,))  #this works
assert isinstance(fake_count.call_args.kwargs, (dict,))  # doesn't work

What am I doing wrong here?

Wayne Werner
  • 49,299
  • 29
  • 200
  • 290
  • Fun fact (although possibly related), `isinstance(fake_count.call_args.blablabla, (mock._Call,))` is `True` as well (and indeed this applies to any attribute one might attempt to access) – Matias Cicero Feb 07 '20 at 00:49
  • So I discovered :P I *can* access the element via the tuple-ish access, but... the docs *say* there's args and kwargs, dang it! – Wayne Werner Feb 07 '20 at 00:54
  • All you are doing is calling a `Mock` object. The fact that it patches anything is irrelevant, both because you aren't using the patched name, and because you are calling something that is known to just be a `Mock`. Perhaps you want to use `wraps`, so that calling `count` will both call the original object *and* collect information about the call that you can query. – chepner Feb 07 '20 at 18:47
  • 1
    Upvoted simply because of the chuckle factor (#montypython) – Vince I May 13 '22 at 14:44

1 Answers1

14

This is a feature introduced in Python 3.8 in this issue.

The 3.7 documentation does not mention it (while the newest docs do) - so you have to access the arguments by index in Python < 3.8.

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46