Короткий ответ
Тестовую функцию объявляют как async def и помечают @pytest.mark.asyncio (или используют asyncio_mode="auto"), чтобы pytest-asyncio сам создал event loop, запустил корутину теста и корректно его закрыл; основная ловушка — использование объектов, созданных в разных циклах событий, между тестами.
Как это работает подробнее
pytest-asyncio создаёт event loop (область видимости зависит от конфигурации фикстуры event_loop) и выполняет тестовую корутину через него, как обычно делает asyncio.run; это позволяет использовать await прямо в теле тест-функции.
Пример кода
example-280.py
import asyncio
import pytest
async def fetch_value():
await asyncio.sleep(0.01)
return 42
@pytest.mark.asyncio
async def test_fetch_value():
result = await fetch_value()
assert result == 42
# Запуск: pytest -q test_file.py
# Проверка: тест проходит; при ошибке в fetch_value pytest покажет
# стандартный traceback внутри корутины, как для синхронного тестаЧто использовать на практике
- Практика. Нужно явно решить область видимости event loop (per-test, per-module, per-session) в зависимости от того, нужно ли переиспользовать соединения между тестами; для тестирования таймаутов удобно использовать малые значения
asyncio.sleepи проверять порядок завершения через список, а не точное время (оно нестабильно в CI).
Подводные камни
- Риски. Создают долгоживущие асинхронные объекты (например,
aiohttp.ClientSession) в одном event loop и пытаются использовать их в тесте с другим — получают ошибку «attached to a different loop»; вызываютasyncio.run()внутри обычного sync-теста вручную, что плохо сочетается с async-фикстурами pytest-asyncio.