简体   繁体   中英

Mock: assert mock_calls with a numpy array as argument raises ValueError and np.testing.assert_array_equal does not work

I have a mocked object that I would like to check its calls using mock_calls, where it is called with numpy arrays . But the problem is that it raises ValueError, as shown in the following simple toy example.

>>> mocked_model_called_with_np_array = mock.Mock()
>>> mocked_model_called_with_np_array(np.array([1, 2]))
>>> mocked_model_called_with_np_array.mock_calls
[call(array([1, 2]))]

Now I set the expected calls:

>>> expected_call_with_numpy = [mock.call(np.array([1, 2]))]

Now if I check it as shown below, it raises error:

>>> assert expected_call_with_numpy == mocked_model_called_with_np_array.mock_calls

---------------------------------------------------------------------------
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-61-9806e62badf5> in <module>
----> 1 assert expected_call_with_numpy == mocked_model_called_with_np_array.mock_calls

c:\..\python\python36\lib\unittest\mock.py in __eq__(self, other)
   2053 
   2054         # this order is important for ANY to work!
-> 2055         return (other_args, other_kwargs) == (self_args, self_kwargs)
   2056 
   2057 

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

My search on stackoverflow and the found solutions:

HERE it is suggested to use np.testing.assert_array_equal while you have numpy arrays, but this also does not solve my problems as shown below.

>>> np.testing.assert_array_equal(expected_call_with_numpy, mocked_model_called_with_np_array.mock_calls)

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-57-4a0373c94354> in <module>
----> 1 np.testing.assert_array_equal(expected_call_with_numpy, mocked_model_called_with_np_array.mock_calls)

c:\...\python\python36\lib\site-packages\numpy\testing\utils.py in assert_array_equal(x, y, err_msg, verbose)
    852     __tracebackhide__ = True  # Hide traceback for py.test
    853     assert_array_compare(operator.__eq__, x, y, err_msg=err_msg,
--> 854                          verbose=verbose, header='Arrays are not equal')
    855 
    856 

c:\...\python\python36\lib\site-packages\numpy\testing\utils.py in assert_array_compare(comparison, x, y, err_msg, verbose, header, precision, equal_nan, equal_inf)
    776                                 names=('x', 'y'), precision=precision)
    777             if not cond:
--> 778                 raise AssertionError(msg)
    779     except ValueError:
    780         import traceback

AssertionError: 
Arrays are not equal

(mismatch 100.0%)
 x: array([['', (array([1, 2]),), {}]], dtype=object)
 y: array([['', (array([1, 2]),), {}]], dtype=object)

Note that the arrays are the same, but it produces error!

Can anyone comment on how to use mock_calls for a mockec object called with a numpy array and then check if the mock_calls produce the expected calls? eg, something like below

assert expected_call_with_numpy == mocked_model_called_with_np_array.mock_calls

Ran in to the same problem trying to check if mocked calls contained a specific numpy array. Turns out that the call object is indexable so you can also do stuff like this:

>>> import numpy as np
>>> from unittest import mock
>>> mock_object = mock.MagicMock()
>>> mock_object(1, np.array([1, 2, 3]), a=3)
>>> mock_object(10, np.array([10, 20, 30]), b=30)
>>> calls = mock_object.call_args_list
>>> calls
[call(1, array([1, 2, 3]), a=3), call(10, array([10, 20, 30]), b=30)]
>>> calls[0]
call(1, array([1, 2, 3]), a=3)
>>> calls[0][0]
(1, array([1, 2, 3]))
>>> calls[0][1]
{'a': 3}

So that instead of asserting that the calls are equal you can just check that the calls were made and that arguments passed were correct individually, ugly but it works:

>>> assert mock_object.call_count == 2
>>> assert calls[0][0][0] == 1
>>> np.testing.assert_array_equal(calls[0][0][1], np.array([1, 2, 3]))
>>> assert calls[0][1]['a'] == 3
...

One can track the numpy arguments passed to any method of a class, simply by creating a fake (mock) class. For example, if I want to check the numpy calls to method bar of an object of class Foo , I can do as follows:

class MockFoo():
    called_by = []
    def bar(self, *args):
        self.called_by.extend([*args])

Now, we have:

>>> a = MockFoo()
>>> a.bar(numpy.array([1, 2]))
>>> a.bar(numpy.array([100, 200]))
>>> a.bar(numpy.array([10000, 20000]))

Now we can simply check the calls to foo.bar as below:

>>> a.called_by 
[array([1, 2]), array([100, 200]), array([10000, 20000])]

The main problem comes from numpy overriding == operator, returning an array instead of single boolean value.

There is a way around that, though. If you use callee library to check call arguments, you can use callee.general.Matching and provide a lambda to check equality.

Here's how it works with np.allclose

mocked_fn.assert_called_with(
  callee.general.Matching(lambda x: np.allclose(x, my_array))
)

Note: I'm not associated with callee library in any way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM