Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking whether function has been called multiple times with different parameters

Assume we have a function f(x,y) and another function

def g():
       # ...
       f(i,j) # i,j vary and f is called multiple times
       # ...

We want to write a Unit test that checks whether f is called the number of times and with the right parameters.

def test_g():
      with patch('mymodule.f') as function:
          assert function.gacs.call_count == correct_no_calls

There is

function.assert_called_with(...)

but this only refers to the last call. So assuming g calls f(1,2) and then f(2,3), function.assert_called_with(1,2) is False.

Furthermore, there is

function.call_args_list

which yields a list of call objects with the right parameters. Comparing this list to call object that we create in the unit test feels like a very nasty thing to do. call seems like an internal class of the mock library.

Is there a better way to do this? I use this set-up to test parallel execution of an apply function.

like image 944
Joachim Avatar asked Dec 11 '15 14:12

Joachim


2 Answers

Even @MartinPieters's answer is correct I think that is not the best way to do it. Mock provide assert_has_calls to do this kind of duties.

Your test could be:

function.assert_has_calls([mock.call(1, 2), mock.call(2, 3)])

Where mock.call is a helper class do to these kind of jobs.

Pay attention that is a has call and means the call list should be in the list of call and not equal. To solve it I usually define my own helper assert_is_calls() as follow

def assert_is_calls(m, calls, any_order=False):
   assert len(m.mock_calls) == len(calls)
   m.assert_has_calls(calls, any_order=any_order)

That a resume example

>>> import mock
>>> f = mock.Mock()
>>> f(1)
<Mock name='mock()' id='139836302999952'>
>>> f(2)
<Mock name='mock()' id='139836302999952'>
>>> f.assert_has_calls([mock.call(1), mock.call(2)])
>>> f.assert_has_calls([mock.call(2), mock.call(1)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/damico/.local/lib/python2.7/site-packages/mock/mock.py", line 969, in assert_has_calls
    ), cause)
  File "/home/damico/.local/lib/python2.7/site-packages/six.py", line 718, in raise_from
    raise value
AssertionError: Calls not found.
Expected: [call(2), call(1)]
Actual: [call(1), call(2)]
>>> f.assert_has_calls([mock.call(2), mock.call(1)], any_order=True)
>>> f(3)
<Mock name='mock()' id='139836302999952'>
>>> f.assert_has_calls([mock.call(2), mock.call(1)], any_order=True)
>>> f.assert_has_calls([mock.call(1), mock.call(2)])
>>> assert len(f.mock_calls)==2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>>> assert len(f.mock_calls)==3
>>> def assert_is_calls(m, calls, any_order=False):
...    assert len(m.mock_calls) == len(calls)
...    m.assert_has_calls(calls, any_order=any_order)
... 
>>> assert_is_calls(f, [mock.call(1), mock.call(2), mock.call(3)])
>>> assert_is_calls(f, [mock.call(1), mock.call(3), mock.call(2)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in assert_is_calls
  File "/home/damico/.local/lib/python2.7/site-packages/mock/mock.py", line 969, in assert_has_calls
    ), cause)
  File "/home/damico/.local/lib/python2.7/site-packages/six.py", line 718, in raise_from
    raise value
AssertionError: Calls not found.
Expected: [call(1), call(3), call(2)]
Actual: [call(1), call(2), call(3)]
>>> assert_is_calls(f, [mock.call(1), mock.call(3), mock.call(2)], True)
>>> assert_is_calls(f, [mock.call(1), mock.call(3)], True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in assert_is_calls
AssertionError
>>> 
like image 130
Michele d'Amico Avatar answered Oct 14 '22 11:10

Michele d'Amico


Test if the Mock().mock_calls list is equal to a list of mock.call() objects you provide:

self.assertEquals(function.mock_calls, [
    mock.call(1, 2),
    mock.call(2, 3),
])

This gives you precise control, requiring both the order and the number of calls to match.

The mock.call() class is not internal, it is meant to be used for assertions like these.

like image 9
Martijn Pieters Avatar answered Oct 14 '22 12:10

Martijn Pieters