Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assert a monkey patch was called in pytest?

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.

like image 297
ked Avatar asked Dec 15 '20 14:12

ked


People also ask

What is monkeypatch in Pytest?

monkeypatch can be used to patch functions dependent on the user to always return a specific value. In this example, monkeypatch. setattr is used to patch Path. home so that the known testing path Path("/abc") is always used when the test is run. This removes any dependency on the running user for testing purposes.

Is monkeypatch part of Pytest?

This test function utilizes the 'monkeypatch' fixture that is part of pytest, which means that the 'monkeypatch' fixture is passed into the function as an argument. The test function starts by creating a mock version of the getcwd() function (the 'mock_getcwd()' function) which returns a specified value.

Why do we need monkey patching?

Monkey patching is reopening the existing classes or methods in class at runtime and changing the behavior, which should be used cautiously, or you should use it only when you really need to. As Python is a dynamic programming language, Classes are mutable so you can reopen them and modify or even replace them.


Video Answer


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"
like image 104
Tommy Avatar answered Oct 19 '22 18:10

Tommy


You need a Mock object to call assert_called_with - monkeypatch does not provide that out of the box. 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.

like image 8
MrBean Bremen Avatar answered Oct 19 '22 20:10

MrBean Bremen