Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make Mock.assert_called_with() agnostic to args vs kwargs

Unit-tests should test functionality and try to be agnostic to implementation details. Mock.assert_called_with() is a convenient function, yet AFAIK it compares *args to *args and **kwargs to **kwargs. Therefore:

# class to be mocked during test
class SomeClass():
    def func(self,a,b,c=5):
        # ...

# code under test
somaclass_instance.func(1,b=2,c=3)

# test code that works
someclass_mock.func.assert_called_with(1,b=2,c=3)

# test code that won't work
someclass_mock.func.assert_called_with(1,2,c=3)
someclass_mock.func.assert_called_with(a=1,b=2,c=3)

is there a way to generalize this so that the specifics of which *args where used as **kwargs in the call to func, which is really an implementation detail, will be ignored?

like image 706
Jonathan Livni Avatar asked Feb 05 '12 17:02

Jonathan Livni


2 Answers

Since Python 3.4, just like you wanted, asserting for specific call signatures is normalized automatically when a callable Mock is created with a spec, and for object methods when using auto-speccing.

From the very end of the documentation of the Mock class:

A callable mock which was created with a spec (or a spec_set) will introspect the specification object’s signature when matching calls to the mock. Therefore, it can match the actual call’s arguments regardless of whether they were passed positionally or by name:

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, c=3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(1, 2, 3)
>>> mock.assert_called_with(a=1, b=2, c=3)

This applies to assert_called_with(), assert_called_once_with(), assert_has_calls() and assert_any_call(). When Autospeccing, it will also apply to method calls on the mock object.

Changed in version 3.4: Added signature introspection on specced and autospecced mock objects.

like image 67
taleinat Avatar answered Nov 14 '22 01:11

taleinat


File a feature request to mock.

Fundamental problem is that without access to real function/class mock has no way of knowing the order of keyword arguments, that is invocations call(a=1, b=2) and call(b=2, a=1) look identical to mock, while invocations call(1, 2) and call(2, 1) do not.

If you wish to generalize mock, you will need to pass a call prototype or a function in lieu of prototype, e.g:

amock.afunc.assert_called_with(1, 2, c=3, __prototype__=lambda a=None, b=None, c=None: None)
like image 29
Dima Tisnek Avatar answered Nov 13 '22 23:11

Dima Tisnek