Kludex / fastapi-tips

FastAPI Tips by The FastAPI Expert!
2.09k stars 79 forks source link

[feature request] `AsyncClient`+ `pytest` fixture #25

Open baggiponte opened 1 month ago

baggiponte commented 1 month ago

Ciao! 😊

Was following tip number 5 and use httpx AsyncClient over the TestClient. So:

import pytest
from httpx import ASGITransport, AsyncClient

from path.to.app import app

@pytest.mark.asyncio
async def test_app_starts() -> None:
    async with AsyncClient(
        transport=ASGITransport(app),
        base_url="http://localhost:8080",
    ) as client:
        response = await client.get("/")
    assert response.status_code == 200

I'd like to make a fixture out of the client. So I tried this:

from collections.abc import AsyncGenerator

import pytest
from httpx import ASGITransport, AsyncClient

from path.to.app import app

@pytest.fixture
async def client() -> AsyncGenerator[AsyncClient, None]:
    async with AsyncClient(
        transport=ASGITransport(app),
        base_url="http://localhost:8080",
    ) as client:
        yield client

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

However when I use the fixture I get the following error:

AttributeError: 'async_generator' object has no attribute 'get'

Do you have some guidance? 😊

Kludex commented 1 month ago

It's a pytest-asyncio problem.

I don't know what is it exactly, but if you use @pytest_asyncio.fixture instead of @pytest.fixture it solves the issue.

I use @pytest.mark.anyio instead of @pytest.mark.asyncio since anyio is already a dependency.

baggiponte commented 1 month ago

It's a pytest-asyncio problem.

I don't know what is it exactly, but if you use @pytest_asyncio.fixture instead of @pytest.fixture it solves the issue.

It works! 😊 Thanks. If you think it's worthy an addition, I'd be glad to open a PR (even just a callout on the page).

I use @pytest.mark.anyio instead of @pytest.mark.asyncio since anyio is already a dependency.

Tried this out but errors since it requires trio (which shouldn't be a required dependency, I think?).

Kludex commented 1 month ago

Tried this out but errors since it requires trio (which shouldn't be a required dependency, I think?).

This should set only asyncio, instead of using both asyncio and trio.

@pytest.fixture
def anyio_backend():
    return 'asyncio'

It works! 😊 Thanks. If you think it's worthy an addition, I'd be glad to open a PR (even just a callout on the page).

I think it would make sense to recommend pytest.mark.anyio instead of pytest.mark.asyncio just for convenience, since you already have it installed... I would accept a PR with this recommendation.

baggiponte commented 1 month ago

Works! Will add this to the backlog and open a PR soon (hopefully).

Kludex commented 1 month ago

Thanks.