Notarás que las funciones de testing son def normales, no async def.
Y las llamadas al cliente también son llamadas normales, sin usar await.
Esto te permite usar pytest directamente sin complicaciones.
Detalles Técnicos
También podrías usar from starlette.testclient import TestClient.
FastAPI proporciona el mismo starlette.testclient como fastapi.testclient solo como una conveniencia para ti, el desarrollador. Pero viene directamente de Starlette.
Consejo
Si quieres llamar funciones async en tus pruebas además de enviar requests a tu aplicación FastAPI (ej. funciones asíncronas de base de datos), revisa las Pruebas Asíncronas en el tutorial avanzado.
Luego podrías tener un archivo test_main.py con tus pruebas. Podría vivir en el mismo paquete de Python (el mismo directorio con un archivo __init__.py):
Luego podrías actualizar test_main.py con las pruebas extendidas:
fromfastapi.testclientimportTestClientfrom.mainimportappclient=TestClient(app)deftest_read_item():response=client.get("/items/foo",headers={"X-Token":"coneofsilence"})assertresponse.status_code==200assertresponse.json()=={"id":"foo","title":"Foo","description":"There goes my hero",}deftest_read_item_bad_token():response=client.get("/items/foo",headers={"X-Token":"hailhydra"})assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_read_nonexistent_item():response=client.get("/items/baz",headers={"X-Token":"coneofsilence"})assertresponse.status_code==404assertresponse.json()=={"detail":"Item not found"}deftest_create_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foobar","title":"Foo Bar","description":"The Foo Barters"},)assertresponse.status_code==200assertresponse.json()=={"id":"foobar","title":"Foo Bar","description":"The Foo Barters",}deftest_create_item_bad_token():response=client.post("/items/",headers={"X-Token":"hailhydra"},json={"id":"bazz","title":"Bazz","description":"Drop the bazz"},)assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_create_existing_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foo","title":"The Foo ID Stealers","description":"There goes my stealer",},)assertresponse.status_code==409assertresponse.json()=={"detail":"Item already exists"}
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
fromfastapi.testclientimportTestClientfrom.mainimportappclient=TestClient(app)deftest_read_item():response=client.get("/items/foo",headers={"X-Token":"coneofsilence"})assertresponse.status_code==200assertresponse.json()=={"id":"foo","title":"Foo","description":"There goes my hero",}deftest_read_item_bad_token():response=client.get("/items/foo",headers={"X-Token":"hailhydra"})assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_read_nonexistent_item():response=client.get("/items/baz",headers={"X-Token":"coneofsilence"})assertresponse.status_code==404assertresponse.json()=={"detail":"Item not found"}deftest_create_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foobar","title":"Foo Bar","description":"The Foo Barters"},)assertresponse.status_code==200assertresponse.json()=={"id":"foobar","title":"Foo Bar","description":"The Foo Barters",}deftest_create_item_bad_token():response=client.post("/items/",headers={"X-Token":"hailhydra"},json={"id":"bazz","title":"Bazz","description":"Drop the bazz"},)assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_create_existing_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foo","title":"The Foo ID Stealers","description":"There goes my stealer",},)assertresponse.status_code==409assertresponse.json()=={"detail":"Item already exists"}
Siempre que necesites que el cliente pase información en la request y no sepas cómo hacerlo, puedes buscar (en Google) cómo hacerlo en httpx, o incluso cómo hacerlo con requests, ya que el diseño de HTTPX está basado en el diseño de Requests.
Luego simplemente haces lo mismo en tus pruebas.
Por ejemplo:
Para pasar un parámetro de path o query, añádelo a la URL misma.
Para pasar un body de JSON, pasa un objeto de Python (ej. un dict) al parámetro json.
Si necesitas enviar Form Data en lugar de JSON, usa el parámetro data en su lugar.
Para pasar headers, usa un dict en el parámetro headers.
Para cookies, un dict en el parámetro cookies.
Para más información sobre cómo pasar datos al backend (usando httpx o el TestClient) revisa la documentación de HTTPX.
Nota
Notarás que el TestClient recibe datos que pueden ser convertidos a JSON, no modelos de Pydantic.
Si tienes un modelo de Pydantic en tu prueba y quieres enviar sus datos a la aplicación durante el testing, puedes usar el jsonable_encoder descrito en JSON Compatible Encoder.