You can also use the ANY
helper to always match arguments you don't know or aren't checking for.
More on the ANY helper: https://docs.python.org/3/library/unittest.mock.html#any
So for instance you could match the argument 'session' to anything like so:
from unittest.mock import ANY
requests_arguments = {'slug': 'foo', 'session': ANY}
requests.post.assert_called_with(requests_arguments)
As far as I know Mock
doesn't provide a way to achieve what you want via assert_called_with
. You could access the call_args
and call_args_list
members and perform the assertions manually.
However the is a simple (and dirty) way of achieving almost what you want. You have to implement a class whose __eq__
method always returns True
:
def Any(cls):
class Any(cls):
def __eq__(self, other):
return True
return Any()
Using it as:
In [14]: caller = mock.Mock(return_value=None)
In [15]: caller(1,2,3, arg=True)
In [16]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=True)
In [17]: caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-17-c604faa06bd0> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(int), Any(int), arg=False)
/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724 if self.call_args != (args, kwargs):
725 msg = self._format_mock_failure_message(args, kwargs)
--> 726 raise AssertionError(msg)
727
728
AssertionError: Expected call: mock(0, 0, 0, arg=False)
Actual call: mock(1, 2, 3, arg=True)
As you can see it only checks for the arg
. You have to create subclasses of int
, otherwise the comparisons wont work1. However you still have to provide all the arguments. If you have many arguments you might shorten your code using tuple-unpacking:
In [18]: caller(1,2,3, arg=True)
In [19]: caller.assert_called_with(*[Any(int)]*3, arg=True)
Except for this I cannot think of a way to avoid passing all parameters to assert_called_with
and work it as you intend.
The above solution can be extended to check for types of other arguments. For example:
In [21]: def Any(cls):
...: class Any(cls):
...: def __eq__(self, other):
...: return isinstance(other, cls)
...: return Any()
In [22]: caller(1, 2.0, "string", {1:1}, [1,2,3])
In [23]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(list))
In [24]: caller(1, 2.0, "string", {1:1}, [1,2,3])
In [25]: caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-25-f607a20dd665> in <module>()
----> 1 caller.assert_called_with(Any(int), Any(float), Any(str), Any(dict), Any(tuple))
/usr/lib/python3.3/unittest/mock.py in assert_called_with(_mock_self, *args, **kwargs)
724 if self.call_args != (args, kwargs):
725 msg = self._format_mock_failure_message(args, kwargs)
--> 726 raise AssertionError(msg)
727
728
AssertionError: Expected call: mock(0, 0.0, '', {}, ())
Actual call: mock(1, 2.0, 'string', {1: 1}, [1, 2, 3])
however this doesn't allow arguments that can be, for example, both an int
or a str
. Allowing multiple arguments to Any
and using multiple-inheritance wont help. We can solve this using abc.ABCMeta
def Any(*cls):
class Any(metaclass=abc.ABCMeta):
def __eq__(self, other):
return isinstance(other, cls)
for c in cls:
Any.register(c)
return Any()
Example:
In [41]: caller(1, "ciao")
In [42]: caller.assert_called_with(Any(int, str), Any(int, str))
In [43]: caller("Hello, World!", 2)
In [44]: caller.assert_called_with(Any(int, str), Any(int, str))
1 I used the name Any
for the function since it is "used as a class" in the code. Also any
is a built-in...
@mock.patch.object(module, 'ClassName')
def test_something(self, mocked):
do_some_thing()
args, kwargs = mocked.call_args
self.assertEqual(expected_url, kwargs.get('url'))
see: calls-as-tuples
TLDR:
args, kwargs = requests.post.call_args_list[-1]
self.assertTrue('slug' in kwargs, '`slug` not passed to requests.post')
In simple words, we get access to a tuple with all the positional arguments and a dictionary with all the named arguments that were passed to the function - so now you can check anything you want.
I find this approach much cleaner than the other popular answers because:
If there are too many parameters being passed and only one of them is to be checked, doing something like {'slug': 'foo', 'field1': ANY, 'field2': ANY, 'field3': ANY, ' . . . }
can be clumsy.
Furthermore, if you wanted to check the datatype of a few fields:
args, kwargs = requests.post.call_args_list[0]
self.assertTrue(isinstance(kwargs['data'], dict))
Also, if you're passing arguments (instead of keyword arguments), you can access them via args
like this:
self.assertEqual(
len(args), 1,
'post called with different number of arguments than expected'
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With