Асинхронные тесты
Вы уже видели, как тестировать ваши приложения FastAPI, используя предоставленные TestClient. До сих пор вы видели только как писать синхронные тесты, без использования async функций.
Возможность использовать асинхронные функции в тестах может быть полезна, например, при асинхронном запросе к вашей базе данных. Представьте, что вы хотите протестировать отправку запросов вашему приложению FastAPI и затем проверить, что ваш бэкэнд успешно записал правильные данные в базу данных, используя асинхронную библиотеку базы данных.
Давайте посмотрим, как это можно сделать.
pytest.mark.anyio
Если мы хотим вызывать асинхронные функции в наших тестах, наши тестовые функции должны быть асинхронными. AnyIO предоставляет удобный плагин для этого, позволяющий указать, что некоторые тестовые функции должны вызываться асинхронно.
HTTPX
Даже если ваше приложение FastAPI использует обычные def функции вместо async def, это по-прежнему приложение async.
TestClient выполняет некоторую магию внутри, чтобы вызывать асинхронное приложение FastAPI в ваших обычных def тестовых функциях, используя стандартный pytest. Но эта магия больше не работает, когда мы используем её внутри асинхронных функций. Запуская наши тесты асинхронно, мы больше не можем использовать TestClient внутри наших тестовых функций.
TestClient основан на HTTPX, и, к счастью, мы можем использовать его напрямую для тестирования API.
Пример
Для простого примера рассмотрим структуру файлов, аналогичную той, что описана в Больших приложениях и Тестировании:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
В файле main.py будет:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Tomato"}
В файле test_main.py будут тесты для main.py, они могут выглядеть так:
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"}
Запуск
Вы можете запустить тесты как обычно, используя:
$ pytest
---> 100%
Подробно
Маркер @pytest.mark.anyio сообщает pytest, что эта тестовая функция должна вызываться асинхронно:
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"}
Подсказка
Обратите внимание, что тестовая функция теперь async def вместо просто def, как раньше, когда использовался TestClient.
Затем мы можем создать AsyncClient с приложением и отправить асинхронные запросы к нему, используя 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"}
Это эквивалентно:
response = client.get('/')
…что мы использовали для отправки запросов с помощью TestClient.
Подсказка
Обратите внимание, что мы используем async/await с новым AsyncClient — запрос асинхронный.
Предупреждение
Если ваше приложение полагается на события жизненного цикла, AsyncClient не будет вызывать эти события. Чтобы гарантировать их срабатывание, используйте LifespanManager из florimondmanca/asgi-lifespan.
Другие вызовы асинхронных функций
Поскольку функция тестирования теперь асинхронна, вы можете также вызывать (и await) другие async функции, помимо отправки запросов вашему приложению FastAPI в ваших тестах, точно так же, как вы бы их вызывали в любом другом месте кода.
Подсказка
Если при интеграции асинхронных вызовов функций в ваших тестах (например, при использовании MongoDB's MotorClient) вы столкнулись с ошибкой, помните, что объекты, нуждающиеся в цикле событий, следует создавать только внутри асинхронных функций, например, в '@app.on_event("startup") обработчике.
© 2018 Sebastián Ramírez
Licensed under the MIT License.
https://fastapi.tiangolo.com/advanced/async-tests/