TestClient là client đồng bộ: nó tự dựng một event loop bên trong để chạy app, nên thân test không await được các hàm async khác (gọi trực tiếp repository async, kiểm tra DB async). AsyncClient + ASGITransport giữ test nằm trong cùng một event loop với app.
import pytest
from httpx import ASGITransport, AsyncClient
@pytest.mark.anyio
async def test_create_order():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
r = await ac.post("/orders", json={"sku": "A1"})
assert r.status_code == 201
assert await repo.count_orders() == 1 # possible only in async testHai điểm hay vấp:
- ASGITransport gọi app in-process, không mở cổng mạng, nhưng không chạy lifespan. Nếu app khởi tạo engine hay client trong lifespan, phải bọc thêm LifespanManager (gói asgi-lifespan) hoặc tự khởi tạo trong fixture.
- Cần khai báo backend cho anyio, thường là fixture anyio_backend trả về "asyncio", nếu không test sẽ bị bỏ qua hoặc lỗi plugin.
Với test chỉ kiểm tra status code và JSON, TestClient vẫn đủ và ngắn hơn. Chuyển sang AsyncClient khi test cần thao tác async ngoài lời gọi HTTP.