292

Mock has a helpful assert_called_with() method. However, as far as I understand this only checks the last call to a method.
If I have code that calls the mocked method 3 times successively, each time with different parameters, how can I assert these 3 calls with their specific parameters?

KevinG
  • 882
  • 3
  • 16
  • 33
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359

4 Answers4

293

assert_has_calls is another approach to this problem.

From the docs:

assert_has_calls (calls, any_order=False)

assert the mock has been called with the specified calls. The mock_calls list is checked for the calls.

If any_order is False (the default) then the calls must be sequential. There can be extra calls before or after the specified calls.

If any_order is True then the calls can be in any order, but they must all appear in mock_calls.

Example:

>>> from unittest.mock import call, Mock
>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)

Source: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls

Rohitashwa Nigam
  • 399
  • 5
  • 16
Pigueiras
  • 18,778
  • 10
  • 64
  • 87
  • 12
    A bit weird they chose to add a new "call" type for which they could also have just used a list or a tuple... – jaapz Jan 20 '15 at 10:36
  • 4
    @jaapz It subclasses `tuple`: `isinstance(mock.call(1), tuple)` gives `True`. They also added some methods and attributes. – jpmc26 Sep 16 '15 at 04:50
  • 26
    Early versions of Mock used a plain tuple, but it turns out to be awkward to use. Each function call receives a tuple of (args, kwargs), so to check that "foo(123)" was called correctly, you need to "assert mock.call_args == ((123,), {})", which is a mouthful compared to "call(123)" – Jonathan Hartley Jan 25 '16 at 20:52
  • What do you do when on each instance of call you expect a different return value ? – CodeWithPride Aug 08 '17 at 19:47
  • 3
    @CodeWithPride it looks more a job for `side_effect` – Pigueiras Aug 08 '17 at 23:40
  • Thanks @Pigueiras .. I figured it out. But now I have another issue, I have a function that modifies one of the objects passes to it as argument and I later use that object to call another function. How do I test this scenario ? I tried using side_effect for that too, but looks like side_effect is called before mock checks for the arguments being passed to actual call, so its failing the test. Thanks – CodeWithPride Aug 09 '17 at 17:04
  • @CodeWithPride could you give me an example with some code, please? Maybe you can open a new question with it :) – Pigueiras Aug 09 '17 at 21:15
  • Handy that you can pass `any_order=True` :) – Aldo 'xoen' Giambelluca Apr 05 '18 at 13:48
  • I think there may be a bug with `assert_has_calls`. It started splitting up the input string and placing newlines in random places... I used `assert_any_call` instead and it worked like a charm. (tested with python 3.9, pytest-mock 3.6.1) – ashrasmun Dec 18 '21 at 11:41
  • That sounds like a bug in your code, not with assert_has_calls. – NotoriousPyro Jan 19 '23 at 14:53
  • This can be combined with an assertion on `call_count` to ensure the mock is called with exactly the specified calls. In your example, the following will ensure mock is not called with an additional calls than those specified: mock.assert_has_calls([call(1), call(2), call(3), call(4)]) assert mock.call_count == 4 – laker93 Apr 17 '23 at 09:10
177

Usually, I don't care about the order of the calls, only that they happened. In that case, I combine assert_any_call with an assertion about call_count.

>>> import mock
>>> m = mock.Mock()
>>> m(1)
<Mock name='mock()' id='37578160'>
>>> m(2)
<Mock name='mock()' id='37578160'>
>>> m(3)
<Mock name='mock()' id='37578160'>
>>> m.assert_any_call(1)
>>> m.assert_any_call(2)
>>> m.assert_any_call(3)
>>> assert 3 == m.call_count
>>> m.assert_any_call(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "[python path]\lib\site-packages\mock.py", line 891, in assert_any_call
    '%s call not found' % expected_string
AssertionError: mock(4) call not found

I find doing it this way to be easier to read and understand than a large list of calls passed into a single method.

If you do care about order or you expect multiple identical calls, assert_has_calls might be more appropriate.

Edit

Since I posted this answer, I've rethought my approach to testing in general. I think it's worth mentioning that if your test is getting this complicated, you may be testing inappropriately or have a design problem. Mocks are designed for testing inter-object communication in an object oriented design. If your design is not objected oriented (as in more procedural or functional), the mock may be totally inappropriate. You may also have too much going on inside the method, or you might be testing internal details that are best left unmocked. I developed the strategy mentioned in this method when my code was not very object oriented, and I believe I was also testing internal details that would have been best left unmocked.

KevinG
  • 882
  • 3
  • 16
  • 33
jpmc26
  • 28,463
  • 14
  • 94
  • 146
  • @jpmc26 could you elaborate more on your edit? What do you mean by 'best left unmocked'? How else would you test if a call has been made within a method – otgw Dec 15 '15 at 16:04
  • @memo Often, it is better to let the real method be called. If the other method is broken, it might break the test, but the value of avoiding that is smaller than the value of having a simpler, more maintainable test. The best times to mock are when the external call to the other method is *what you want to test* (Usually, this means some kind of result is passed into it and the code under test doesn't return a result.) or the other method has external dependencies (database, websites) you want to eliminate. (Technically, the last case is more of a stub, and I would hesitate to assert on it.) – jpmc26 Dec 15 '15 at 16:32
  • @jpmc26 mocking is useful when you would like to avoid dependency injection or some other method of runtime strategy choosing. like you mentioned, testing inner logic of methods, without calling external services, and more importantly, without being environment aware (a no no for good code to have `do() if TEST_ENV=='prod' else dont()`), is achieved easily by mocking the way you suggested. a side effect of this is being to maintain tests per versions (say code changes between google search api v1 and v2, your code will test version 1 no matter what) – Daniel Dror Oct 27 '16 at 12:43
  • @DanielDubovski Most of your testing should be input/output based. That's not always possible, but if it's not possible most of the time, you probably have a design problem. When you need some value returned that normally comes from another piece of code and you want to cut a dependency, a stub will usually do. Mocks are only necessary when you need to verify that some state modifying function (probably with no return value) is called. (The difference between a mock and a stub is that you don't assert on a call with a stub.) Using mocks where stubs will do makes your tests less maintainable. – jpmc26 Oct 27 '16 at 17:12
  • @jpmc26 isn't calling an external service a kind of output? of course you can refactor out the code that builds the message to be sent and test it instead of asserting the call params, but IMHO, it's pretty much the same. How would you suggest to redesign calling external API's ?I agree that mocking should be cut down to a minimum, all Im saying is you cant go around testing the data you send to external services to make sure the logic is behaving as expected. – Daniel Dror Oct 30 '16 at 08:50
  • @DanielDubovski Actually, you can. Create a function that builds the arguments to the service. Create a function that takes those arguments as input, makes the request, and returns the output. Now have another function that accepts that output as an argument. Unit test the argument builder and the service output user, and don't test the service caller. 100% coverage isn't an absolute requirement. Each unit test should provide more value (confidence that it works, detection of bugs) than cost (maintainability, etc.). – jpmc26 Oct 30 '16 at 16:20
72

You can use the Mock.call_args_list attribute to compare parameters to previous method calls. That in conjunction with Mock.call_count attribute should give you full control.

KevinG
  • 882
  • 3
  • 16
  • 33
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359
  • 8
    `assert_has_calls` only checks if the expected calls have been done, but not if that are the only ones. – blueyed Jun 01 '16 at 12:13
26

I always have to look this one up time and time again, so here is my answer.


Asserting multiple method calls on different objects of the same class

Suppose we have a heavy duty class (which we want to mock):

In [1]: class HeavyDuty(object):
   ...:     def __init__(self):
   ...:         import time
   ...:         time.sleep(2)  # <- Spends a lot of time here
   ...:     
   ...:     def do_work(self, arg1, arg2):
   ...:         print("Called with %r and %r" % (arg1, arg2))
   ...:  

here is some code that uses two instances of the HeavyDuty class:

In [2]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(13, 17)
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(23, 29)
   ...:    


Now, here is a test case for the heavy_work function:

In [3]: from unittest.mock import patch, call
   ...: def test_heavy_work():
   ...:     expected_calls = [call.do_work(13, 17),call.do_work(23, 29)]
   ...:     
   ...:     with patch('__main__.HeavyDuty') as MockHeavyDuty:
   ...:         heavy_work()
   ...:         MockHeavyDuty.return_value.assert_has_calls(expected_calls)
   ...:  

We are mocking the HeavyDuty class with MockHeavyDuty. To assert method calls coming from every HeavyDuty instance we have to refer to MockHeavyDuty.return_value.assert_has_calls, instead of MockHeavyDuty.assert_has_calls. In addition, in the list of expected_calls we have to specify which method name we are interested in asserting calls for. So our list is made of calls to call.do_work, as opposed to simply call.

Exercising the test case shows us it is successful:

In [4]: print(test_heavy_work())
None


If we modify the heavy_work function, the test fails and produces a helpful error message:

In [5]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(113, 117)  # <- call args are different
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(123, 129)  # <- call args are different
   ...:     

In [6]: print(test_heavy_work())
---------------------------------------------------------------------------
(traceback omitted for clarity)

AssertionError: Calls not found.
Expected: [call.do_work(13, 17), call.do_work(23, 29)]
Actual: [call.do_work(113, 117), call.do_work(123, 129)]


Asserting multiple calls to a function

To contrast with the above, here is an example that shows how to mock multiple calls to a function:

In [7]: def work_function(arg1, arg2):
   ...:     print("Called with args %r and %r" % (arg1, arg2))

In [8]: from unittest.mock import patch, call
   ...: def test_work_function():
   ...:     expected_calls = [call(13, 17), call(23, 29)]    
   ...:     with patch('__main__.work_function') as mock_work_function:
   ...:         work_function(13, 17)
   ...:         work_function(23, 29)
   ...:         mock_work_function.assert_has_calls(expected_calls)
   ...:    

In [9]: print(test_work_function())
None


There are two main differences. The first one is that when mocking a function we setup our expected calls using call, instead of using call.some_method. The second one is that we call assert_has_calls on mock_work_function, instead of on mock_work_function.return_value.

Pedro M Duarte
  • 26,823
  • 7
  • 44
  • 43