Testing is the fastest feedback loop for service reliability. pytest dominates Python testing, and httpx provides an async HTTP client. This post uses FastAPI’s TestClient heritage plus httpx.AsyncClient so async routes feel natural.
Key terms
- pytest: The de facto Python test runner with function-based tests and rich plugins, ideal for FastAPI.
- httpx.AsyncClient: An async HTTP client perfect for hitting FastAPI routes in event-loop-friendly tests.
- Fixture: A pytest function that prepares shared resources such as clients or fake users before each test.
- @pytest.mark.asyncio: A decorator that lets pytest run async test functions on an event loop.
Practice card
- Estimated time: 50 minutes
- Prereqs: Mini service from Part 12, pytest installed
- Goal: Test FastAPI endpoints with httpx.AsyncClient and dependency overrides
Prepare the test environment
Install the base packages:
pip install pytest httpx pytest-asyncio
Define the app and dependency overrides in conftest.py so every test gets the same wiring:
# tests/conftest.py
from httpx import AsyncClient
from app.main import app
from app.db.session import override_session
@pytest.fixture(autouse=True)
def override_dependencies():
app.dependency_overrides[get_session] = override_session
yield
app.dependency_overrides.clear()
@pytest.fixture
async def client():
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
override_session supplies an in-memory SQLite session for tests. With that stub you avoid spinning up the real DB.
TestClient vs httpx.AsyncClient
fastapi.testclient.TestClientwrapsrequestsfor quick synchronous tests.- When routes and dependencies are async-heavy,
httpx.AsyncClientkeeps the code natural.
The exercises here use httpx because the series increasingly leans on async endpoints.
CRUD test example
@pytest.mark.asyncio
async def test_create_task(client):
payload = {"title": "First task"}
response = await client.post("/tasks", json=payload)
assert response.status_code == 201
data = response.json()
assert data["title"] == "First task"
assert data["done"] is False
@pytest.mark.asyncio sets up the event loop. Skipping it triggers RuntimeError: no running event loop.
Stub the auth flow
Avoid logging in for every test by overriding the dependency that returns the current user:
@pytest.fixture
return User(id=1, email="[email protected]")
@pytest.fixture(autouse=True)
app.dependency_overrides[get_current_user] = lambda: fake_user
yield
Test the authentication pipeline once, then rely on stubs elsewhere to stay fast.
httpx against live servers
AsyncClient(app=app) calls the ASGI app in-process. If you need to hit a running uvicorn instance, open the client with base_url="http://127.0.0.1:8000" and ensure the server is already running. CI rarely uses this because it adds another moving piece.
Coverage structure
Test Suite --> (API Layer)
Test Suite --> (Service Layer)
Service Layer --> (DB Stub)
The suite talks to the API and service layers but hits a stubbed DB. Reserve real DB calls for a handful of integration tests.
Command cheat sheet
pytest -q
pytest --maxfail=1 --disable-warnings -q
pytest --cov=app --cov-report=term-missing
--maxfail=1 stops on the first failure so you can debug faster. Add your go-to commands to package scripts so the team shares one standard.
Expected output
Successful test runs should look like this:
:::terminal{title="pytest run", showFinalPrompt="false"}
[
{ "cmd": "uv run pytest -q", "output": "tests/test_auth.py ..\ntests/test_tasks.py ...\ntests/test_health.py .\n6 passed in 0.84s", "delay": 500 }
]
:::
- Checkpoint: the log shows which files passed.
- Checkpoint: the last line reports the pass count and duration.
- Checkpoint: when failures occur you can pinpoint the exact test.
Practice
- Follow along: write a
clientfixture and a CRUD test, then runpytest -q. - Extend: add an auth stub to exercise protected routes plus 400/404 cases.
- Debug: compare TestClient vs AsyncClient behavior and adjust
pytest-asynciowhen event loop conflicts appear. - Done when: at least three tests pass and failure logs clearly highlight the root cause.
Wrap-up
Even small FastAPI services need tests. Once pytest and httpx become muscle memory you can refactor settings, move infrastructure, or ship new features without fear.
💬 댓글
이 글에 대한 의견을 남겨주세요