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-asynciois the async test the natural. The@pytest.mark.asynciothe mark, theawaitthe natural, theasyncio_modethestrict(the mark required) or theauto(the no mark) - the plugin the full (the mark, the fixture, the loop). - The
asyncio.runis the no-plugin fallback. The sync test wraps the coroutine (theasyncio.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.fixtureis the async fixture. Theasync deffixture (theawaitsetup/teardown), theyieldthe value, thepytest-asynciothe loop the run - theasyncio_mode = "auto"the no-decorator. - The
event_loopis the function scope the default. The per-test loop (the fresh, the no cross-test state), theevent_loop_policythe - 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.