Test your API with pytest — auth, RBAC, and CRUD.
- pytest basics for FastAPI
- TestClient for HTTP requests
- Fixtures for database and auth tokens
- What each test file covers
- Catch bugs before deployment
- Document expected behavior
- Safe to refactor when tests pass
| Tool | Purpose |
|---|---|
| pytest | Test runner |
| TestClient | Simulates HTTP requests without running server |
| In-memory SQLite | Fresh DB per test — no pollution |
tests/
├── conftest.py # Shared fixtures (DB, tokens, client)
└── api/v1/
├── test_auth.py # Register, login, refresh
├── test_rbac.py # Admin vs user permissions
└── test_tasks.py # CRUD + ownership
Key fixtures:
| Fixture | Purpose |
|---|---|
client |
TestClient for HTTP calls |
setup_database |
Creates tables + seed users (autouse) |
user_token |
JWT for regular user |
admin_token |
JWT for admin |
auth_headers |
{"Authorization": "Bearer ..."} |
DB override:
app.dependency_overrides[get_db] = override_get_dbTests use in-memory SQLite instead of real task_api.db.
make test
# or
pytest tests/ -v
pytest tests/api/v1/test_auth.py -v # single filecd backend-with-fastapi/task-api
source .venv/bin/activate
make testExpected: 15 passed
Open tests/api/v1/test_auth.py → test_register_and_login — follow the flow.
Change TaskService.create_task to always raise an error — run tests — see which fail — fix it.
Add to test_tasks.py:
def test_create_task_requires_title(client, auth_headers):
response = client.post(
"/api/v1/tasks",
json={"title": "", "status": "pending"},
headers=auth_headers,
)
assert response.status_code == 422Run: pytest tests/api/v1/test_tasks.py::test_create_task_requires_title -v
| Test file | Covers |
|---|---|
test_auth.py |
Register, login, refresh, /me, duplicate email |
test_rbac.py |
Admin list users, user denied, admin sees all tasks |
test_tasks.py |
CRUD, pagination, auth required, ownership 403 |
| health/ready | DB connectivity |
| Mistake | Fix |
|---|---|
| Tests hit real database | Use dependency override + in-memory DB |
| Rate limit breaks tests | limiter.enabled = False in conftest |
| Forgetting auth headers | Use auth_headers fixture |