Skillversitas

Testing Async Code

The async def test needs an event loop to run, and pytest does not provide one by default - the pytest-asyncio plugin adds the @pytest.mark.asyncio mark (the coroutine the the run) and the event_loop fixture (the loop the). The no-plugin fallback is the asyncio.run in the sync test (the coroutine the). This chapter covers the pytest-asyncio (the install, the mark, the asyncio_mode), the asyncio.run fallback (the no-plugin), the fixture the async (the @pytest_asyncio.fixture), and the event loop scope (the function the default).

The pytest-asyncio: The Mark

# the pytest-asyncio: the
#   the
#   the
import pytest

@pytest.mark.asyncio
async def test_async_fetch():
    from texttools import fetch_text
    body = await fetch_text("https://example.com")
    assert body != ""
# the
#   the
#   the

The pytest-asyncio plugin adds the @pytest.mark.asyncio mark: the async def test is collected as the coroutine, and the plugin runs it on the event loop (the asyncio.run the per-test, the await the natural). The asyncio_mode ini option is the strict (the default, the mark required) or the auto (the every async def test the run, the no mark) - the strict the explicit (the mark the intent), the auto the convenience (the no mark, the). The event_loop fixture is the loop (the function scope the default, the event_loop_policy the), and the @pytest_asyncio.fixture is the async fixture (the async def fixture the loop the run) - the next section. The no-plugin fallback (the next section) is the asyncio.run in the sync test, and the rule is the one: the pytest-asyncio is the async test the natural (the await, the mark), the asyncio.run is the sync wrapper (the no plugin, the one-line).

The asyncio.run Fallback

# the asyncio.run: the the SYNC wrapper (the
#   the
#   the)
import asyncio
from texttools import fetch_text

def test_async_no_plugin():
    body = asyncio.run(fetch_text("https://example.com"))
    assert body != ""
# the
#   the
#   the

The asyncio.run is the no-plugin fallback: the sync test wraps the coroutine (the asyncio.run(fetch_text(...)) the run, the await the internal, the result the return) - the no pytest-asyncio, the no mark, the one-line wrapper. The limit is the fixture: the async def fixture needs the loop the run, and the asyncio.run is the test's wrapper, not the fixture's - the @pytest_asyncio.fixture (the next section) is the async fixture the loop the run. The rule is the one: the pytest-asyncio is the async test the natural (the await, the mark, the async fixture), the asyncio.run is the sync wrapper (the no plugin, the one-line) - the two are the async test's two paths, and the pytest-asyncio the full (the fixture, the loop scope), the asyncio.run the minimal.

The Async Fixture

# the @pytest_asyncio.fixture: the the ASYNC fixture
# (the
#   the)
import pytest_asyncio

@pytest_asyncio.fixture
async def client():
    from texttools import AsyncClient
    c = AsyncClient("https://api.example.com")
    await c.open()
    yield c
    await c.close()
# the
#   the

@pytest.mark.asyncio
async def test_client(client):
    body = await client.get("/health")
    assert body.status == 200

The @pytest_asyncio.fixture is the async fixture: the async def fixture (the await c.open() the setup, the yield the value, the await c.close() the teardown) runs on the loop the pytest-asyncio provides (the), and the @pytest.mark.asyncio test requests it (the client the parameter, the await the natural). The asyncio_mode = "auto" (the ini) makes the async def fixture the auto-async (the no @pytest_asyncio.fixture, the @pytest.fixture the async) - the auto the convenience, the strict the explicit. The rule is the one: the async code is the pytest-asyncio (the mark, the async fixture, the loop scope), the no-plugin is the asyncio.run (the sync wrapper, the no async fixture) - the next chapter's capsys/caplog is the output capture the async test also uses.

The Working Rules

  • The pytest-asyncio is the async test the natural. The @pytest.mark.asyncio the mark, the await the natural, the asyncio_mode the strict (the mark required) or the auto (the no mark) - the plugin the full (the mark, the fixture, the loop).
  • The asyncio.run is the no-plugin fallback. The sync test wraps the coroutine (the asyncio.run(coro) the run, the result the return) - the no mark, the one-line, the limit the async fixture (the no loop the run).
  • The @pytest_asyncio.fixture is the async fixture. The async def fixture (the await setup/teardown), the yield the value, the pytest-asyncio the loop the run - the asyncio_mode = "auto" the no-decorator.
  • The event_loop is the function scope the default. The per-test loop (the fresh, the no cross-test state), the event_loop_policy the - the loop the test's, the teardown the loop's close.

Summary

The async def test needs the event loop, and the pytest-asyncio plugin provides it: the @pytest.mark.asyncio the mark (the coroutine the run, the await the natural), the asyncio_mode the strict (the mark required) or the auto (the no mark), and the @pytest_asyncio.fixture the async fixture (the await setup/teardown, the yield the value). The event_loop fixture is the loop (the function scope the default, the per-test fresh), and the no-plugin fallback is the asyncio.run in the sync test (the one-line wrapper, the limit the async fixture). The rule is the split: the pytest-asyncio is the async test the natural (the mark, the async fixture, the loop scope), the asyncio.run is the sync wrapper (the no plugin, the one-line) - the next chapter's capsys/capfd/caplog is the output capture, the print and the logging the test inspects, the -s the live.