Pytest-asyncio is a pytest plugin that provides support for testing asynchronous code based on asyncio, Python's standard library for writing concurrent code using the async/await syntax.
Pytest-asyncio helps test asynchronous code by:
- Providing fixtures for testing asyncio coroutines
- Managing the asyncio event loop during tests
- Supporting both function and class-based async tests
- Handling test teardown properly for async resources
- Integrating seamlessly with pytest's existing features
- Supporting Python 3.7+ asyncio features
Pytest-asyncio is included as a development dependency:
# Install with other development dependencies
uv sync --devTo install it directly:
uv pip install pytest-asyncioIn this project, Pytest-asyncio is used to:
- Test asynchronous functions and coroutines
- Ensure proper handling of async resources
- Validate async API behavior
- Test concurrent operations
Pytest-asyncio is configured in the pyproject.toml file:
[tool.pytest.ini_options]
asyncio_mode = "auto"This configuration enables automatic detection of async tests, which means:
- Tests defined with
async defare automatically treated as asyncio tests - The event loop is automatically created and managed for each test
# Simple async test
async def test_async_function():
result = await my_async_function()
assert result == expected_value
# Using async fixtures
@pytest.fixture
async def async_resource():
resource = await create_resource()
yield resource
await resource.close()
async def test_with_async_fixture(async_resource):
result = await async_resource.operation()
assert result == expected_value# Run all tests (including async tests)
uv run pytest
# Run specific async tests
uv run pytest test_async_module.pyimport pytest
@pytest.fixture
async def api_client():
client = AsyncAPIClient()
await client.connect()
yield client
await client.disconnect()
async def test_api_get_data(api_client):
data = await api_client.get_data("resource_id")
assert "key" in data
assert data["status"] == "active"import asyncio
async def test_concurrent_operations():
# Run multiple operations concurrently
results = await asyncio.gather(operation1(), operation2(), operation3())
# Check results
assert results[0] == expected1
assert results[1] == expected2
assert results[2] == expected3Pytest-asyncio supports different modes for handling the asyncio event loop:
auto: Automatically detects async tests and fixturesstrict: Requires explicit marking of async tests with@pytest.mark.asynciolegacy: Uses a single event loop for all tests (deprecated)
- Use appropriate fixtures: Create async fixtures for resource management.
- Clean up resources: Always clean up async resources in fixture teardown.
- Test error handling: Test both success and error paths in async code.
- Avoid mixing sync and async: Keep synchronous and asynchronous code separate.
- Test timeouts: Use
asyncio.wait_for()to test timeout behavior. - Test cancellation: Verify that your async code handles cancellation correctly.
- Use
asyncio.gather: Test concurrent operations withasyncio.gather.
| Issue | Solution |
|---|---|
| Event loop is closed | Use asyncio_mode = "auto" in pytest configuration |
| Fixture teardown not running | Ensure you're using yield instead of return in async fixtures |
| Test hangs indefinitely | Add timeouts to your async operations with asyncio.wait_for() |
| Mixing sync and async code | Use asyncio.run() or loop.run_until_complete() to call async from sync |