Async Tests

RoadmapsPython Backend

Scenario

You write an `async def test_my_func():` and pytest skips it, or gives a warning about an un-awaited coroutine.

Mental model

Pytest is a synchronous runner by default. If a test is a coroutine (`async def`), pytest just calls it (which returns a coroutine object) and immediately moves on without running it on an event loop. You need a plugin to provide the event loop.

Deep dive

To test `async` functions, you must install `pytest-asyncio`. This plugin automatically provisions an event loop for your tests.

Any test defined as `async def` must be decorated with `@pytest.mark.asyncio`, or you must set `asyncio_mode = auto` in your `pytest.ini` so it detects them automatically.

Code examples

Failing: un-awaited coroutine

async def test_fetch_data():
    data = await fetch_data()
    assert data is not None
# Pytest will warn: coroutine 'test_fetch_data' was never awaited

Standard pytest doesn't know how to run an event loop to execute the coroutine.

Working Async Test

import pytest

@pytest.mark.asyncio
async def test_fetch_data():
    data = await fetch_data()
    assert data['status'] == 'ok'

With `pytest-asyncio`, the decorator tells pytest to spin up an event loop, run the coroutine to completion, and tear down the loop.

Common mistakes

Recall questions

Questions & answers

If you have an `async` database driver, how must you write your test fixture to provide a session?

The fixture must be an `async def` function, yielding the async session. The tests consuming it must also be `async def` and marked with `@pytest.mark.asyncio`.

What happens if an `async` test hangs indefinitely?

Usually it means a task was left awaiting a lock or a network call that never resolved, or there is a sync blocking call starving the test's event loop.

Continue learning

Previous: Pytest Basics and Fixtures

Return to Python Backend Roadmap