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
Project structure:
app/
...
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 app.main import app
client = TestClient(app)
def test_signup():
response = client.post("/auth/signup", json={
"username": "testuser",
"email": "test@example.com",
"hashed_password": "plaintextpassword"
})
assert response.status_code == 201
data = response.json()
assert "user_id" in data
def test_login():
response = client.post("/auth/login", data={
"username": "testuser",
"password": "plaintextpassword"
})
assert response.status_code == 200
data = response.json()
assert "access_token" in data
🔍 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 app.main import app
client = TestClient(app)
def test_create_and_get_task():
# First login to get token
login = client.post("/auth/login", data={
"username": "testuser",
"password": "plaintextpassword"
})
token = login.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
# Create a task
response = client.post("/tasks/", json={"title": "My Task"}, headers=headers)
assert response.status_code == 201
task = response.json()
assert task["title"] == "My Task"
# Get tasks
response = client.get("/tasks/", headers=headers)
assert response.status_code == 200
tasks = response.json()
assert any(t["title"] == "My Task" for t in tasks)
🔍 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