如何在pytest中断言monkey patch已被调用?

10

请考虑以下内容:

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',
        },
    )

我该如何断言我所修补的 get 方法被调用,并传入了 '/endpoint'headers? 当我运行测试时,我会得到以下失败信息:FAILED test/utility/test_api_session.py::test_api_session_get - AttributeError: 'MonkeyPatch' object has no attribute 'assert_called_with'。 我在这里做错了什么? 提前感谢所有回复我的人。
2个回答

15

我打算增加另一个使用monkeypatch而不是"你不能使用monkeypatch"的响应。

由于Python有闭包,这里是我用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

在你的情况下,由于我们正在使用类似的方法处理程序修补HTTP服务器,因此你可以做类似以下的事情(并不是说这很好看):

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"

12
你需要一个`Mock`对象来调用`assert_called_with` - `monkeypatch`不能直接提供这个功能。你可以使用`unittest.mock.patch`和`side_effect`来实现这个功能:
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',
        },
    )

使用`side_effect`是为了仍然能够获得一个模拟对象(在这种情况下是`MagickMock`类型的`mocked`),而不仅仅是在`patch`中设置自己的对象,否则您将无法使用`assert_called_...`方法。
为了澄清:当我写“monkeypatch不会直接提供这个功能”时,这是与使用`assert_called...`方法相关的。这并不意味着您不能使用monkeypatch来实现类似的功能,正如Tommy的答案中很好地展示的那样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接