pytest_httpx icon indicating copy to clipboard operation
pytest_httpx copied to clipboard

No longer works with starlette/fastapi TestClient

Open sscherfke opened this issue 1 year ago • 4 comments

Follow-up of https://github.com/Colin-b/pytest_httpx/issues/144#issuecomment-2071887265


The following example works with pytest-httpx 0.26 but fails with pytest-httpx 0.30:

import fastapi
import httpx
import pytest
import pytest_asyncio
import pytest_httpx


@pytest_asyncio.fixture
async def client() -> fastapi.FastAPI:
    app = fastapi.FastAPI()

    @app.get("/")
    def get() -> None:
        return None

    async with httpx.AsyncClient(
        transport=httpx.ASGITransport(app=app),
        base_url="http://test",
    ) as api_client:
        yield api_client


@pytest.mark.asyncio
async def test_ok(client: httpx.AsyncClient) -> None:
    response = await client.get("/")
    assert response.status_code == 200


@pytest.mark.asyncio
async def test_err(
    client: httpx.AsyncClient, httpx_mock: pytest_httpx.HTTPXMock
) -> None:
    httpx_mock.add_response(status_code=500, text="Internal Server Error")
    response = await client.get("/")
    assert response.status_code == 500

httpx.ASGITransport inherits httpx.AsyncBaseTransport and pytest-httpx only mocks httpx.AsyncHTTPTransport.

I don't think it would be a good idea to mock the base transport, because its handle-Method is "abstract" and should remain so.

Maybe it should mock the asgi/wsgi transport in addition to the http transport or allow passing a list of transport classes to mock.

The Starlette test client inherts httpx.BaseTransport: https://github.com/encode/starlette/blob/master/starlette/testclient.py#L239

My current workaround is this fixture:

@pytest.fixture
def httpx_testclient_mock(monkeypatch: pytest.MonkeyPatch) -> HTTPXMock:
    # This fixture is a workaound for
    # https://github.com/Colin-b/pytest_httpx/issues/144
    mock = HTTPXMock()

    def mocked_handle_request(
        transport: httpx.HTTPTransport, request: httpx.Request
    ) -> httpx.Response:
        return mock._handle_request(transport, request)  # noqa: SLF001

    monkeypatch.setattr(
        starlette.testclient._TestClientTransport,  # noqa: SLF001
        "handle_request",
        mocked_handle_request,
    )

    return mock

sscherfke avatar Oct 24 '24 17:10 sscherfke

Hello @sscherfke

This usage was not part of the non regression suite, and was never documented. However I agree it would be a good addition if most parameters still make sense.

I will have a look at it and see what can be done here.

Thanks for reporting

Colin-b avatar Oct 25 '24 22:10 Colin-b

Thx, let me know if I can help you with anything <3

sscherfke avatar Oct 26 '24 07:10 sscherfke

This feature (compatibility with fastapi TestClient) would be incredibly helpful for me. Is this something that a new contributor would be able to do quickly?

qthequartermasterman avatar Feb 27 '25 00:02 qthequartermasterman

An updated work around for version 0.35.0:

@pytest.fixture
def httpx_testclient_mock(monkeypatch: pytest.MonkeyPatch, httpx_mock:pytest_httpx.HTTPXMock) -> pytest_httpx.HTTPXMock:
    # This fixture is a workaround for
    # https://github.com/Colin-b/pytest_httpx/issues/165

     # Mock TestClient Requests
    real_handle_request = starlette.testclient._TestClientTransport.handle_request

    def mocked_handle_request(
        transport: starlette.testclient._TestClientTransport, request: httpx.Request
    ) -> httpx.Response:
        if httpx_mock._options.should_mock(request):
            return httpx_mock._handle_request(transport, request)
        return real_handle_request(transport, request)

    monkeypatch.setattr(
        starlette.testclient._TestClientTransport,
        "handle_request",
        mocked_handle_request,
    )

qthequartermasterman avatar Feb 27 '25 16:02 qthequartermasterman