Search code examples
pythonmockingpython-unittest

mock object library ANY not working as expected


I'm currently trying to mock a patch request to a server and I'm trying to make use of the ANY attribute in the mock object library. I have the following code:

@patch('path_to_patch.patch')
def test_job_restarted_succesfully(mock_patch):
   make_patch_call()
   mock_patch.assert_called_with(url=ANY, payload=ANY, callback=ANY, async_run=ANY, kwargs=ANY)

I'm getting the following error:

AssertionError: Expected call: patch(async_run=<ANY>, callback=<ANY>, kwargs=<ANY>, payload=<ANY>, url=<ANY>)
E           Actual call: patch(async_run=True, callback=<function JobSvc.send_job_patch_request.<locals>.retry_on_404 at 0x000002752B873168>, payload={'analyzer': {'state': 'started'}, 'meta': {}}, svc_auth=UUID('40ed1a00-a51f-11eb-b1ed-b46bfc345269'), url='http://127.0.0.1:8080/rarecyte/1.0/jobs/slide1@20210422_203831_955885')

I found ANY in the docs given below and can't figure out why assert_called_once_with() is expecting the actual parameter that's called. Here is the relevant section in the docs: https://docs.python.org/3/library/unittest.mock.html#any

EDIT: The make_patch_call() ultimately calls this patch function after computing all the parameters needed for the patch function.

def patch(self, url, payload, callback=None, async_run=False, **kwargs):
    payload = self._serialize_payload(payload)
    func = self._do_async_request if async_run else self._do_request
    return func('patch', (url, payload), callback, kwargs)

Solution

  • For assert_called_with, the arguments and the used keywords have to exactly match. Substituting an argument for ANY will always match the argument value, but the keyword must still match the used keyword. The generic keywords args and kwargs are no exception: if you expect them, they have to be used in the call to match.

    In this case, the kwargs keyword in the expected call:

    mock_patch.assert_called_with(url=ANY, payload=ANY, callback=ANY, async_run=ANY, kwargs=ANY)
    

    has to be changed to the really used keyword svc_auth:

    mock_patch.assert_called_with(url=ANY, payload=ANY, callback=ANY, async_run=ANY, svc_auth=ANY)
    

    Note that the same applies for keyword versus positional arguments, which is a common pitfall. If you have a function foo(bar), then you have to expect the call exactly as it is made, e.g:

    @mock.patch("my_module.foo")
    def test_foo(patched):
        foo(42)
        patched.assert_called_with(ANY)  # passes
        patched.assert_called_with(bar=ANY)  # fails
    
        foo(bar=42)
        patched.assert_called_with(ANY)  # fails
        patched.assert_called_with(bar=ANY)  # passes