python Monkeypatch/模拟HTTPX外部请求

js81xvg6  于 2022-12-28  发布在  Python
关注(0)|答案(1)|浏览(112)

我正在尝试monkeypatch外部请求。下面是一个Web端点的代码:

import httpx, json
...
@app.get('/test')
async def view_test(request):
    async with httpx.AsyncClient() as client:
# sending external request
        api_response = await client.get(
            f'https://jsonplaceholder.typicode.com/todos/1',
            timeout=10,
        )
        resp = api_response.json()
# modifying the result
        resp['foo'] = 0
# forwarding the modified result back to the user
        return HTTPResponse(json.dumps(resp), 200)

当用户向/test发送GET请求时,它会请求一个外部API(JSONPlaceholder),获取JSON结果并将'foo' = 0添加到其中。然后,它会将结果转发回用户。下面是Postman结果:

{
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false,
    "foo": 0
}

下面是我的pytest代码:

import httpx, pytest
...
# The `client` parameter is the fixture of web app
def test_view_test(client, monkeypatch):
    async def return_mock_response(*args, **kwargs):
        return httpx.Response(200, content=b'{"response": "response"}')

    monkeypatch.setattr(httpx.AsyncClient, 'get', return_mock_response)
    _, response = client.test_client.get('/test')
    assert response.json == {'response': 'response', 'foo': 0}
    assert response.status_code == 200

我使用pytest的monkeypatch fixture来模拟HTTPX请求的结果{"response": "response"},所以基本上我所期望的是端点将'foo' = 0添加到模拟结果中,但是它返回的是未修改的{"response": "response"},下面是pytest -vv命令的回溯:

>       assert response.json == {'response': 'response', 'foo': 0}
E       AssertionError: assert {'response': 'response'} == {'response': 'response', 'foo': 0}
E         Common items:
E         {'response': 'response'}
E         Right contains 1 more item:
E         {'foo': 0}
E         Full diff:
E         - {'foo': 0, 'response': 'response'}
E         ?  ----------
E         + {'response': 'response'}

有人能帮我解释为什么端点不修改httpx.AsyncClient().get模拟结果吗?我使用sanic==22.9.0作为后端,httpx==0.23.0作为请求,pytest==7.2.0作为测试。
预期获得{'response': 'response', 'foo': 0},而不是获得{"response": "response"}-模拟的httpx响应的未修改结果。

cgfeq70w

cgfeq70w1#

问题是sanic-testing在幕后使用httpx。所以,当你monkeypatching httpx时,你也会影响测试客户端。因为你只想模拟传出的调用,我们需要排除那些受到影响的调用。
我对@srbssv on Discord的评论是使用一个自定义函数monkeypatch httpx,该函数将检查请求的位置。如果是内部Sanic应用程序,则按原样继续。如果不是,则返回一个mock对象。
基本上是这样的:

from unittest.mock import AsyncMock

import pytest
from httpx import AsyncClient, Response
from sanic import Sanic, json

@pytest.fixture
def httpx():
    orig = AsyncClient.request
    mock = AsyncMock()

    async def request(self, method, url, **kwargs):
        if "127.0.0.1" in url:
            return await orig(self, method, url, **kwargs)
        return await mock(method, url, **kwargs)

    AsyncClient.request = request
    yield mock
    AsyncClient.request = orig

@pytest.fixture
def app():
    app = Sanic("Test")

    @app.post("/")
    async def handler(_):
        async with AsyncClient() as client:
            resp = await client.get("https://httpbin.org/get")
            return json(resp.json(), status=resp.status_code)

    return app

def test_outgoing(app: Sanic, httpx: AsyncMock):
    httpx.return_value = Response(201, json={"foo": "bar"})

    _, response = app.test_client.post("")
    assert response.status == 201
    assert response.json == {"foo": "bar"}
    httpx.assert_awaited_once()

相关问题