Testing FastAPI Applications¶
🎯 What You’ll Learn¶
- The difference between unit tests and integration tests
- How to set up pytest for FastAPI projects
- How to use FastAPI’s
TestClientto simulate requests - Best practices for structuring tests in production
🧠 Step 1: Unit vs Integration Tests¶
| Type | Purpose | Example in FastAPI |
|---|---|---|
| Unit Test | Test a single function or component in isolation | Verify hash_password() returns a bcrypt hash |
| Integration Test | Test multiple components working together | Call /auth/login and check if JWT is returned |
Both are important:
- Unit tests catch logic errors early.
- Integration tests ensure the whole system works correctly.
⚡ Step 2: Setting Up pytest¶
Install pytest:
pip install pytest
Add a tests folder at the root of your project:
...
tests/
test_auth.py
test_tasks.py
pytest automatically discovers files starting with test_.
⚡ Step 3: Using FastAPI’s TestClient¶
FastAPI provides TestClient (built on requests) to simulate API calls.
📄 tests/test_auth.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
# Disable rate limiting so tests don't fail on repeated runs
app.state.limiter.enabled = False
def test_signup():
# We use a unique username to avoid "Username already exists" 400 errors
response = client.post("/auth/signup", json={
"username": "newuser_auth",
"email": "auth@example.com",
"hashed_password": "testpassword123"
})
assert response.status_code == 201
assert "user_id" in response.json()
def test_login():
# 1. Ensure user exists
client.post("/auth/signup", json={
"username": "loginuser",
"email": "login@example.com",
"hashed_password": "testpassword123"
})
# 2. Login using FORM DATA (required by OAuth2PasswordRequestForm)
response = client.post("/auth/login", data={
"username": "loginuser",
"password": "testpassword123"
})
assert response.status_code == 200
data = response.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
🔍 What’s happening?¶
TestClient(app)spins up the FastAPI app in memory.client.post()simulates HTTP requests.- Assertions check status codes and response data.
⚡ Step 4: Integration Test Example¶
📄 tests/test_tasks.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
app.state.limiter.enabled = False
def test_task_lifecycle():
# 1. Setup: Create and Login
username = "taskuser"
password = "taskpassword"
client.post("/auth/signup", json={
"username": username,
"email": "tasks@example.com",
"hashed_password": password
})
login_res = client.post("/auth/login", data={
"username": username,
"password": password
})
token = login_res.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
# 2. Create Task
create_res = client.post(
"/tasks/",
json={"title": "Finish API Tests", "description": "Ensure everything passes"},
headers=headers
)
assert create_res.status_code == 201
task_id = create_res.json()["id"]
# 3. Get Tasks
get_res = client.get("/tasks/", headers=headers)
assert get_res.status_code == 200
assert any(t["id"] == task_id for t in get_res.json())
# 4. Update Task
update_res = client.put(
f"/tasks/{task_id}",
json={"title": "Updated Title", "completed": True},
headers=headers
)
assert update_res.status_code == 200
assert update_res.json()["title"] == "Updated Title"
# 5. Delete Task
del_res = client.delete(f"/tasks/{task_id}", headers=headers)
assert del_res.status_code == 204
🔍 Why this matters?¶
- Tests the full flow: login → create task → fetch tasks.
- Ensures JWT authentication and task ownership work together.
⚡ Step 5: Running Tests¶
Run all tests:
python -m pytest
Run a specific file:
python -m pytest tests/test_auth.py
Run with verbose output:
python -m pytest -v
🧠 Best Practices¶
- Separate unit tests (pure functions) from integration tests (API endpoints).
- Use fixtures for reusable setup (e.g. test database, test users).
- Mock external services (e.g. email, payment APIs) to avoid slow tests.
- Run tests in CI/CD pipelines before deployment.
🧪 Practice Challenge¶
- Write a unit test for
verify_password()to ensure it rejects wrong passwords. - Write an integration test for
/admin/usersthat:- Logs in as a regular user → expects
403 Forbidden. - Logs in as an admin → expects a list of users.
- Logs in as a regular user → expects
- Add a fixture that sets up a temporary in-memory SQLite database for tests.
🧠 Recap¶
You now know how to:
- Write unit and integration tests for FastAPI apps
- Use pytest and
TestClientto simulate requests - Structure tests for maintainability and production readiness