Tests asynchrones¶
🌐 Traduction par IA et humains
Cette traduction a été réalisée par une IA guidée par des humains. 🤝
Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖
Vous pouvez améliorer cette traduction en nous aidant à mieux guider le LLM d'IA.
Vous avez déjà vu comment tester vos applications FastAPI en utilisant le TestClient fourni. Jusqu'à présent, vous n'avez vu que comment écrire des tests synchrones, sans utiliser de fonctions async.
Pouvoir utiliser des fonctions asynchrones dans vos tests peut être utile, par exemple lorsque vous interrogez votre base de données de manière asynchrone. Imaginez que vous vouliez tester l'envoi de requêtes à votre application FastAPI puis vérifier que votre backend a bien écrit les bonnes données dans la base, tout en utilisant une bibliothèque de base de données asynchrone.
Voyons comment procéder.
pytest.mark.anyio¶
Si nous voulons appeler des fonctions asynchrones dans nos tests, nos fonctions de test doivent être asynchrones. AnyIO fournit un plug-in pratique qui nous permet d'indiquer que certaines fonctions de test doivent être appelées de manière asynchrone.
HTTPX¶
Même si votre application FastAPI utilise des fonctions def normales au lieu de async def, c'est toujours une application async en interne.
Le TestClient fait un peu de magie pour appeler l'application FastAPI asynchrone depuis vos fonctions de test def normales, en utilisant pytest standard. Mais cette magie ne fonctionne plus lorsque nous l'utilisons dans des fonctions asynchrones. En exécutant nos tests de manière asynchrone, nous ne pouvons plus utiliser le TestClient dans nos fonctions de test.
Le TestClient est basé sur HTTPX et, heureusement, nous pouvons l'utiliser directement pour tester l'API.
Exemple¶
Pour un exemple simple, considérons une structure de fichiers similaire à celle décrite dans Applications plus grandes et Tests :
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
Le fichier main.py contiendrait :
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Tomato"}
Le fichier test_main.py contiendrait les tests pour main.py, il pourrait maintenant ressembler à ceci :
import pytest
from httpx import ASGITransport, AsyncClient
from .main import app
@pytest.mark.anyio
async def test_root():
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}
Exécuter¶
Vous pouvez lancer vos tests comme d'habitude via :
$ pytest
---> 100%
En détail¶
Le marqueur @pytest.mark.anyio indique à pytest que cette fonction de test doit être appelée de manière asynchrone :
import pytest
from httpx import ASGITransport, AsyncClient
from .main import app
@pytest.mark.anyio
async def test_root():
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}
Astuce
Notez que la fonction de test est maintenant async def au lieu de simplement def comme auparavant avec le TestClient.
Nous pouvons ensuite créer un AsyncClient avec l'application et lui envoyer des requêtes asynchrones en utilisant await.
import pytest
from httpx import ASGITransport, AsyncClient
from .main import app
@pytest.mark.anyio
async def test_root():
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}
C'est l'équivalent de :
response = client.get('/')
... que nous utilisions pour faire nos requêtes avec le TestClient.
Astuce
Notez que nous utilisons async/await avec le nouveau AsyncClient — la requête est asynchrone.
Alertes
Si votre application s'appuie sur des événements de cycle de vie (lifespan), le AsyncClient ne déclenchera pas ces événements. Pour vous assurer qu'ils sont déclenchés, utilisez LifespanManager depuis florimondmanca/asgi-lifespan.
Autres appels de fonctions asynchrones¶
Comme la fonction de test est désormais asynchrone, vous pouvez également appeler (et await) d'autres fonctions async en plus d'envoyer des requêtes à votre application FastAPI dans vos tests, exactement comme vous le feriez ailleurs dans votre code.
Astuce
Si vous rencontrez une erreur RuntimeError: Task attached to a different loop lors de l'intégration d'appels de fonctions asynchrones dans vos tests (par exemple en utilisant MotorClient de MongoDB), n'oubliez pas d'instancier les objets qui ont besoin d'une boucle d'événements uniquement dans des fonctions async, par exemple dans un callback @app.on_event("startup").