Testing asyncio code with pytest-asyncio.
pip install pytest-asyncio
# pytest.ini or pyproject.toml
[pytest]
asyncio_mode = auto # Recommended for pytest-asyncio 0.21+
import pytest
@pytest.mark.asyncio
async def test_async_function():
result = await async_fetch_data()
assert result["status"] == "ok"
@pytest.mark.asyncio
async def test_async_context_manager():
async with AsyncResource() as resource:
result = await resource.get()
assert result is not None
import pytest
import aiohttp
@pytest.fixture
async def async_client():
"""Async fixture with cleanup."""
async with aiohttp.ClientSession() as session:
yield session
# Session closed automatically
@pytest.fixture
async def database():
"""Async database fixture."""
conn = await create_async_connection()
await conn.execute("BEGIN")
yield conn
await conn.execute("ROLLBACK")
await conn.close()
@pytest.mark.asyncio
async def test_with_async_fixture(async_client):
async with async_client.get("https://httpbin.org/json") as resp:
data = await resp.json()
assert "slideshow" in data
@pytest.fixture(scope="session")
async def app():
"""Session-scoped async fixture."""
app = await create_app()
yield app
await app.shutdown()
@pytest.fixture(scope="module")
async def db_pool():
"""Module-scoped connection pool."""
pool = await asyncpg.create_pool(DATABASE_URL)
yield pool
await pool.close()
import asyncio
@pytest.mark.asyncio
async def test_timeout():
with pytest.raises(asyncio.TimeoutError):
async with asyncio.timeout(0.1):
await asyncio.sleep(1.0)
@pytest.mark.asyncio
async def test_wait_for():
with pytest.raises(asyncio.TimeoutError):
await asyncio.wait_for(slow_operation(), timeout=0.1)
@pytest.mark.asyncio
async def test_task_cancellation():
task = asyncio.create_task(long_running_task())
await asyncio.sleep(0.01)
task.cancel()
with pytest.raises(asyncio.CancelledError):
await task
@pytest.mark.asyncio
async def test_graceful_cancellation():
"""Test that cleanup runs on cancellation."""
cleanup_ran = False
async def task_with_cleanup():
nonlocal cleanup_ran
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
cleanup_ran = True
raise
task = asyncio.create_task(task_with_cleanup())
await asyncio.sleep(0.01)
task.cancel()
with pytest.raises(asyncio.CancelledError):
await task
assert cleanup_ran
@pytest.mark.asyncio
async def test_gather_success():
results = await asyncio.gather(
async_op_1(),
async_op_2(),
async_op_3(),
)
assert len(results) == 3
@pytest.mark.asyncio
async def test_gather_with_exceptions():
results = await asyncio.gather(
async_op_1(),
async_op_that_fails(),
async_op_3(),
return_exceptions=True
)
assert isinstance(results[1], Exception)
@pytest.mark.asyncio
async def test_task_group():
results = []
async with asyncio.TaskGroup() as tg:
tg.create_task(append_result(results, 1))
tg.create_task(append_result(results, 2))
tg.create_task(append_result(results, 3))
assert sorted(results) == [1, 2, 3]
@pytest.mark.asyncio
async def test_task_group_exception():
with pytest.raises(ExceptionGroup):
async with asyncio.TaskGroup() as tg:
tg.create_task(successful_task())
tg.create_task(failing_task())
from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_mock_async_function(mocker):
mock = mocker.patch("mymodule.async_api_call", new_callable=AsyncMock)
mock.return_value = {"data": "mocked"}
result = await mymodule.fetch_data()
assert result == {"data": "mocked"}
mock.assert_awaited_once()
@pytest.mark.asyncio
async def test_async_side_effect(mocker):
mock = AsyncMock()
mock.side_effect = [
{"page": 1},
{"page": 2},
ValueError("No more pages"),
]
assert await mock() == {"page": 1}
assert await mock() == {"page": 2}
with pytest.raises(ValueError):
await mock()
import aiohttp
from aiohttp import web
import pytest
@pytest.fixture
async def app():
"""Create aiohttp app."""
app = web.Application()
app.router.add_get("/", home_handler)
return app
@pytest.fixture
async def client(aiohttp_client, app):
"""Create test client."""
return await aiohttp_client(app)
@pytest.mark.asyncio
async def test_endpoint(client):
resp = await client.get("/")
assert resp.status == 200
data = await resp.json()
assert "message" in data
@pytest.mark.asyncio
async def test_websocket(aiohttp_client, app):
client = await aiohttp_client(app)
async with client.ws_connect("/ws") as ws:
await ws.send_str("Hello")
msg = await ws.receive()
assert msg.type == aiohttp.WSMsgType.TEXT
assert msg.data == "Hello back"
import pytest
@pytest.fixture(scope="session")
def event_loop_policy():
"""Custom event loop policy."""
return asyncio.DefaultEventLoopPolicy()
# For uvloop
@pytest.fixture(scope="session")
def event_loop_policy():
import uvloop
return uvloop.EventLoopPolicy()
@pytest.mark.asyncio
async def test_queue_producer_consumer():
queue = asyncio.Queue()
results = []
async def producer():
for i in range(3):
await queue.put(i)
await queue.put(None) # Sentinel
async def consumer():
while True:
item = await queue.get()
if item is None:
break
results.append(item)
await asyncio.gather(producer(), consumer())
assert results == [0, 1, 2]
asyncio_mode = auto - Simplifies test markingrun_in_executor if needed