Короткий ответ
Нужно использовать плагин pytest-asyncio (или pytest-anyio) и помечать тестовую функцию как async def с декоратором @pytest.mark.asyncio, чтобы pytest запустил её внутри event loop, а не просто вызвал как обычную функцию.
Как это работает подробнее
Без специального плагина pytest не умеет ожидать корутины — async def тест без правильной настройки просто вернёт объект корутины, а не выполнит его, из-за чего тест формально «пройдёт», ничего не проверив. pytest-asyncio оборачивает такие тесты в event loop и дожидается их завершения через await.
Пример кода
testing_example_311.py
import pytest
import httpx
@pytest.mark.asyncio
async def test_fetch_user_returns_profile(async_client: httpx.AsyncClient):
response = await async_client.get("/users/1")
assert response.status_code == 200
assert response.json()["id"] == 1Что использовать на практике
- Практика. Важно следить за режимом работы event loop в фикстурах (например, asyncio_mode = "auto" или явные маркеры), чтобы асинхронные фикстуры и тесты использовали один и тот же loop. Для асинхронных HTTP-клиентов используют httpx.AsyncClient с ASGITransport вместо синхронного TestClient, если нужно протестировать именно асинхронный путь выполнения.
Подводные камни
- Риск. Забывать @pytest.mark.asyncio или не настраивать asyncio_mode, из-за чего тест с async def выполняется некорректно и не падает на реальных ошибках внутри корутины, создавая ложное ощущение покрытости. Смешивать блокирующие вызовы (например, обычный requests.get) внутри асинхронного теста, что не отражает реальное поведение production-кода в event loop и маскирует проблему блокировки цикла событий.