Ton API fonctionne, le CRUD est connecté à PostgreSQL. Mais sans tests, chaque modification est un pari. Et sans documentation, tes consommateurs devinent comment l’utiliser. Ce chapitre couvre les deux piliers qui transforment une API “ça marche sur ma machine” en API de production : pytest + TestClient pour les tests, et la documentation OpenAPI que FastAPI génère automatiquement.
Pourquoi tester une API
Un test API vérifie que ton endpoint renvoie la bonne réponse pour une entrée donnée. C’est ton filet de sécurité :
- Confiance — tu déploies en sachant que le CRUD fonctionne
- Régression — un changement dans
crud.pyne casse pas silencieusement/users - Documentation vivante — les tests montrent exactement comment l’API se comporte
- Refactoring serein — tu changes la structure interne sans peur
💡 Règle du 80/20 : teste d’abord les endpoints (tests d’intégration). Les tests unitaires sur les fonctions CRUD viendront après, quand ta couverture de base est solide.
Setup de test avec pytest
Installe les dépendances et configure une base de test isolée :
pip install pytest httpx pytest-cov
Le fichier conftest.py est le cœur du setup — il crée une base SQLite en mémoire et un client de test qui n’a pas besoin de serveur HTTP :
# conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from main import app
from database import Base, get_db
engine = create_engine("sqlite:///./test.db",
connect_args={"check_same_thread": False})
TestSession = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
db = TestSession()
try:
yield db
finally:
db.close()
@pytest.fixture(autouse=True)
def setup_db():
Base.metadata.create_all(bind=engine)
yield
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def client():
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as c:
yield c
app.dependency_overrides.clear()
Points clés :
dependency_overridesremplaceget_dbpar la version test — pas besoin de PostgreSQLautouse=Truerecrée les tables avant chaque test — isolation totaleTestClientexécute les requêtes sans démarrer Uvicorn — rapide et fiable
🔥 Chaque test part d’une base vide. C’est intentionnel : un test qui dépend des données d’un autre test est fragile et casse de manière imprévisible.
Écrire des tests efficaces
Tester le happy path et les erreurs
Un bon test couvre les deux côtés — ce qui marche et ce qui doit échouer :
# test_users.py
def test_create_user(client):
response = client.post("/users", json={
"name": "Alice", "email": "alice@ex.com", "password": "secret123"
})
assert response.status_code == 201
data = response.json()
assert data["name"] == "Alice"
assert "id" in data
assert "password" not in data # jamais exposé
def test_create_user_duplicate_email(client):
user = {"name": "Alice", "email": "alice@ex.com", "password": "secret123"}
client.post("/users", json=user)
response = client.post("/users", json=user)
assert response.status_code == 400
def test_get_user_not_found(client):
response = client.get("/users/9999")
assert response.status_code == 404
def test_create_user_invalid(client):
response = client.post("/users", json={"name": "", "email": "x"})
assert response.status_code == 422
Quatre tests, quatre scénarios : création OK, doublon rejeté, ressource introuvable, données invalides. C’est la base pour chaque ressource CRUD.
Tester les routes protégées
Si ton API utilise l’authentification JWT, crée une fixture qui fournit les headers :
@pytest.fixture
def auth_headers(client):
client.post("/users", json={
"name": "Test", "email": "test@ex.com", "password": "secret123"
})
resp = client.post("/token", data={
"username": "test@ex.com", "password": "secret123"
})
token = resp.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
def test_create_article_authenticated(client, auth_headers):
response = client.post("/users/1/articles", json={
"title": "Mon article", "content": "Contenu suffisant"
}, headers=auth_headers)
assert response.status_code == 201
def test_create_article_no_token(client):
response = client.post("/users/1/articles", json={
"title": "Test", "content": "Contenu"
})
assert response.status_code == 401
⚠️ Teste toujours l’accès sans token. C’est le test le plus important en sécurité — il vérifie que tes routes protégées le sont vraiment.
Lancer et mesurer :
# Tous les tests avec détails
pytest -v
# Avec couverture de code
pytest --cov=. --cov-report=term-missing -v
# Un test spécifique
pytest tests/test_users.py::test_create_user -v
🎯 Vise 80% de couverture minimum sur les endpoints. 100% est rarement rentable — mais en dessous de 80%, tu navigues à l’aveugle.
Documentation OpenAPI automatique
FastAPI génère trois interfaces sans aucune configuration :
/docs— Swagger UI, interactive, permet de tester les endpoints/redoc— ReDoc, lisible, idéale pour les consommateurs/openapi.json— le schéma brut, utilisable par les générateurs de SDK
Tout ce que tu mets dans tes modèles Pydantic (Field, description, example) et tes décorateurs (response_model, status_code, tags) apparaît automatiquement dans la doc.
Personnaliser la documentation
app = FastAPI(
title="Blog API",
description="API de blog : articles, utilisateurs, authentification JWT.",
version="2.0.0",
contact={"name": "DevOps Lab", "url": "https://devopslab.ch"},
openapi_tags=[
{"name": "users", "description": "Gestion des utilisateurs"},
{"name": "articles", "description": "CRUD articles"},
{"name": "auth", "description": "Authentification JWT"},
]
)
@app.get("/articles/{article_id}", tags=["articles"],
response_model=ArticleResponse,
responses={404: {"description": "Article non trouvé"}})
def get_article(article_id: int, db: Session = Depends(get_db)):
"""Récupère un article par son ID."""
...
Les tags regroupent les endpoints par domaine dans Swagger. Les responses documentent les codes d’erreur possibles. Les docstrings deviennent les descriptions des endpoints.
💡 Désactive la doc en production si ton API n’est pas publique : app = FastAPI(docs_url=None, redoc_url=None). La doc expose la surface d’attaque de ton API.
Les pièges classiques
Tests qui dépendent de l’ordre d’exécution. Si test_delete suppose que test_create a tourné avant, ton test est fragile. Chaque test doit créer ses propres données — c’est pour ça qu’on recrée la base à chaque test.
Tester l’implémentation au lieu du comportement. Ne vérifie pas que db.query() est appelé avec les bons arguments. Vérifie que GET /users/1 renvoie un 200 avec les bonnes données. Les tests d’intégration résistent aux refactorings, pas les mocks.
Oublier les cas limites. Le body vide, le JSON malformé, les valeurs aux bornes (priority=0, priority=6), les strings très longues. Pydantic protège bien, mais vérifie que tes 422 sont claires.
La base de test qui fuit. Si tu utilises PostgreSQL pour les tests, assure-toi de rollback entre les tests. SQLite en mémoire est plus simple — mais attention, certains comportements PostgreSQL (JSONB, constraints) ne sont pas identiques en SQLite.
⚠️ N’utilise pas create_all() en production. C’est parfait pour les tests, mais en prod tu veux Alembic pour les migrations — il gère les modifications de schéma sans perdre les données existantes.
Ce qu’on retient
🎯 Les tests et la doc ne sont pas du bonus — c’est ce qui fait la différence entre un prototype et une API de production.
Les essentiels :
- TestClient + SQLite — teste sans serveur HTTP ni PostgreSQL
dependency_overrides— injecte la base de test sans toucher au code prod- 4 tests par ressource minimum — create OK, doublon, not found, validation
- Fixtures pour l’auth —
auth_headersréutilisable dans tous les tests pytest --cov— vise 80% de couverture sur les endpoints- Swagger automatique — tags, responses, descriptions enrichissent la doc sans effort
Tu maîtrises maintenant les fondamentaux de FastAPI — des routes aux tests en passant par la base de données. La prochaine étape : le déploiement avec Docker, Uvicorn et un reverse proxy pour mettre ton API en production. 🚀
🖥️ Pratique sur ton propre serveur
Pour suivre Apprendre FastAPI en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.
Contenu réservé aux abonnés
Ce chapitre fait partie de la formation complète. Abonne-toi pour débloquer tous les contenus.
Débloquer pour 29 CHF/moisLe chapitre 1 de chaque formation est gratuit.
Série pas encore débloquée
Termine la série prérequise d'abord pour accéder à ce contenu.
Aller à la série prérequiseSérie : Apprendre FastAPI
5 / 6Sur cette page
Articles liés
Déployer FastAPI en production
Conteneurise ton API avec Docker, configure Nginx et Uvicorn, mets en place un CI/CD complet et applique les bonnes pratiques de production.
FastAPI : ta première API en Python
Comprends les APIs, le protocole HTTP, l'architecture REST, puis installe FastAPI et crée ton premier endpoint. Le socle pour tout ce qui suit.
Routes, paramètres et validation
Path parameters, query parameters, validation des données avec Pydantic et gestion des headers HTTP dans FastAPI.