FastAPI: From Basics to Production · Testing FastAPI Applications

Async Tests with httpx and pytest

Test async code paths with AsyncClient and ASGITransport.

When tests need to await things (async database fixtures, async services), use httpx.AsyncClient with ASGITransport(app=app) and an async test runner (pytest-asyncio or anyio's pytest plugin). Note that ASGITransport doesn't run lifespan events; use asgi-lifespan's LifespanManager or set up resources in fixtures.

import pytest
from httpx import ASGITransport, AsyncClient
from asgi_lifespan import LifespanManager
from app.main import app


@pytest.fixture
async def client():
    async with LifespanManager(app):
        async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
            yield c


@pytest.mark.anyio
async def test_create_and_fetch_book(client):
    created = await client.post("/books", json={"title": "Dune", "price_cents": 1299})
    assert created.status_code == 201
    fetched = await client.get(f"/books/{created.json()['id']}")
    assert fetched.json()["title"] == "Dune"

Common pitfalls

  • Mixing sync TestClient with async fixtures on different event loops.
  • Forgetting the async marker/config so tests never await.
  • Assuming ASGITransport runs lifespan.

Key points

  • AsyncClient + ASGITransport
  • anyio or pytest-asyncio
  • Handle lifespan explicitly

Further reading: FastAPI: Async tests

Practice questions

When do you need AsyncClient instead of TestClient?

Answer

When the test itself must await async code, such as async database fixtures, so everything runs on the same event loop.

Does ASGITransport trigger lifespan events?

Answer

No. Use a tool like asgi-lifespan's LifespanManager or initialize resources in fixtures.

Open in the app to mark lessons complete, take notes and track your progress.