Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

check unittest.mock call arguments agnostically w.r.t. whether they have been passed as positional arguments or keyword arguments

When a unittest.mock.Mock object has been called, I can check for the argument values with the exact signature of the call:

from unittest.mock import Mock

m = Mock()  # creation of mock
m('foo', bar='baz')  # call to the mock
m.assert_called_once_with('foo', bar='baz')  # check call arguments

Checking for a different signature with the same values will fail. E.g., if we check with 'baz' as a positional argument instead of a named argument, the assertion will fail:

m.assert_called_once_with('foo', 'baz')
# AssertionError: Expected call: mock('foo', 'baz')
# Actual call: mock('foo', bar='baz')

It has to. If the function replaced by m was

def actual_fu(foo, bar):
    # do something

then the calls would be equivalent, but if it was

def a_different_actual_fu(foo, *args, bar='some default'):
    # do something

then the calls would not be equivalent. Mock doesn't know the actual function's signature, so it can't rely on the equivalence we would have in the first case.

Is there a way of checking the call argument values that is agnostic about whether they were passed positionally or as keyword arguments, by letting the Mock (or an assertion helper function or similar) know about the actual function replaced by the mock?

The Mock object can be made aware of the object it replaces (which can be a function or method) with the optional spec argument or with autospeccing, but those serve a different purpose (limiting what calls to allow on the mock) and don't affect after-the-fact checking.

like image 455
das-g Avatar asked Jan 22 '16 11:01

das-g


1 Answers

The Mock object can be made aware of the object it replaces (which can be a function or method) with the optional spec argument or with autospeccing, but those serve a different purpose..

This is exactly what Issue 17015: mock could be smarter and inspect the spec's signature improvement issue was about. The spec actually is very much related and now makes the mock function-signature-aware.

See how mock fails when we assert mock was called with a keyword argument - without letting it know about the actual function signature:

>>> from unittest.mock import Mock
>>>
>>> def actual_fu(foo, bar):
...     pass
>>>
>>> m = Mock()
>>> m('foo', bar='baz')
<Mock name='mock()' id='4356741496'>
>>>
>>> m.assert_called_once_with(foo='foo', bar='baz')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 803, in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 792, in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: mock(bar='baz', foo='foo')
Actual call: mock('foo', bar='baz')

And now, see how it all passes if we provide a spec:

>>> m = Mock(spec=actual_fu)
>>> m('foo', bar='baz')
<Mock name='mock()' id='4356249528'>
>>> 
>>> m.assert_called_once_with(foo='foo', bar='baz')
>>> m.assert_called_once_with('foo', bar='baz')
>>> m.assert_called_once_with(bar='baz', foo='foo')
>>> m.assert_called_once_with('foo', 'baz')
>>>

(Used Python 3.5.1)

like image 181
alecxe Avatar answered Oct 31 '22 21:10

alecxe