2

This question is similar to check unittest.mock call arguments agnostically w.r.t. whether they have been passed as positional arguments or keyword arguments, but with an extension.

Given a mocked function with a given spec, it is possible to check whether a function was called with the right arguments, regardless of whether the function was called with positional arguments or keyword arguments (or a mixture of both). So obviously, mock knows how to translate between those two.

My problem is a little more complicated, because given this signature:

def myfun(a, b, some_list, c=3, d=4)
    # Do something with a, b, c, d and some_list
    return None

all the following calls should be regarded as correct:

myfun(b=2, a=1, some_list = [3, 4, 5])
myfun(1, 2, [5, 4, 3])
myfun(1, 2, d=4, some_list=[4, 3, 5])

The values for a, b, c and d should be 1, 2, 3 and 4 respectively, but the value for some_list should just contain the values [3, 4, 5], without regard to order.

What I would like to do in a unittest is something like this (when complicated_fun should at one point call myfun with the given arguments):

def test_complicated_fun(self):
    myobject = MyClass(...)
    myobject.myfun = Mock(return_value = None, spec=myobject.myfun)
    myobject.complicated_fun()

    myobject.myfun.assert_called_once()
    self.assertEqual(myobject.myfun.call_args['a'], 1)
    self.assertEqual(myobject.myfun.call_args['b'], 2)
    self.assertEqual(myobject.myfun.call_args['c'], 3)
    self.assertEqual(myobject.myfun.call_args['d'], 4)
    self.assertCountEqual(myobject.myfun.call_args['mylist'], [3, 4, 5])

Except this will of course fail, seeing as we can't subscript call_args.

Is there a way around this, and to get the value of mylist directly from the mock? I could make a workaround it by using myobject.myfun.call_args[1].get('mylist', myobject.myfun.call_args[0][2]).

But that:

  • Depends on manually specifying again that mylist is the argument at index 2, meaning if my spec changes I should also manually edit the test.
  • Doesn't feel very pythonic
Emil Bode
  • 1,784
  • 8
  • 16
  • Note that in your example, all parameters are positional arguments, judging by the second call, but in this case the third call will not work. Also, you probably know that using a list as a default argument is not a good idea. – MrBean Bremen Nov 24 '20 at 18:28
  • @MrBeanBremen I fixed some mistakes, but I'm not using a list as default argument. Where the list is specified, it's just in a call; `some_list` doesn't have a default value. – Emil Bode Nov 25 '20 at 10:03
  • Ah, sorry, got that one wrong. Now the example is more clear, thanks. I don't think there is an easy way to achieve what you want to do, but I will have another look later. – MrBean Bremen Nov 25 '20 at 12:16

0 Answers0