How to assert a monkey patch was called in pytest?

Viewed 3758

Consider the following:

class MockResponse:
    status_code = 200

    @staticmethod
    def json():
        return {'key': 'value'}
                                  # where api_session is a fixture
def test_api_session_get(monkeypatch, api_session) -> None:
    def mock_get(*args, **kwargs):
        return MockResponse()

    monkeypatch.setattr(requests.Session, 'get', mock_get)
    response = api_session.get('endpoint/') # My wrapper around requests.Session
    assert response.status_code == 200
    assert response.json() == {'key': 'value'}
    monkeypatch.assert_called_with(
        'endpoint/',
        headers={
            'user-agent': 'blah',
        },
    )

How can I assert that the get I am patching gets called with '/endpoint' and headers? When I run the test now I get the following failure message:

FAILED test/utility/test_api_session.py::test_api_session_get - AttributeError: 'MonkeyPatch' object has no attribute 'assert_called_with'

What am I doing wrong here? Thanks to all those of who reply in advance.

2 Answers

Going to add another response that uses monkeypatch rather than "you can't use monkeypatch"

Since python has closures, here is my poor man's way of doing such things with monkeypatch:

patch_called = False

def _fake_delete(keyname):
    nonlocal patch_called
    patch_called = True
    assert ...

monkeypatch.setattr("mymodule._delete", _fake_delete)
res = client.delete(f"/.../{delmeid}"). # this is a flask client
assert res.status_code == 200
assert patch_called

In your case, since we are doing similar things with patching an HTTP servers method handler, you could do something like (not saying this is pretty):

param_called = None

def _fake_delete(param):
    nonlocal param_called
    patch_called = param
    assert ...

monkeypatch.setattr("mymodule._delete", _fake_delete)
res = client.delete(f"/.../{delmeid}")
assert res.status_code == 200
assert param_called == "whatever this should be"

You need a Mock object to call assert_called_with - monkeypatch does not provide that. You can use unittest.mock.patch with side_effect instead to achieve this:

from unittest import mock
import requests

...

@mock.patch('requests.Session.get')
def test_api_session_get(mocked, api_session) -> None:
    def mock_get(*args, **kwargs):
        return MockResponse()

    mocked.side_effect = mock_get
    response = api_session.get('endpoint/') 
    ...
    mocked.assert_called_with(
        'endpoint/',
        headers={
            'user-agent': 'blah',
        },
    )

Using side_effect is needed to still get a mock object (mocked in this case, of type MagickMock), instead of just setting your own object in patch, otherwise you won't be able to use the assert_called_... methods.

Related