mrkeyoor.com_
Sun 20 Sept 07:01 UTC
PyPITestingupdated 20 Sept 2026

pytest-asyncio review

pytest-asyncio 1.4.0 lets pytest collect and execute `asyncio` coroutine tests and async fixtures. It creates event loops, separates fixture and test loop scopes, supports strict or automatic discovery, and exposes asyncio debug mode. The current release introduces `pytest_asyncio_loop_factories`, which can parametrize tests over named loop implementations, and deprecates replacing the `event_loop_policy` fixture. It also raises the pytest floor to 8.4, makes the unset fixture-loop-scope warning clearer, and closes a loop that could be leaked after synchronous code called `asyncio.run()` or unset the current loop.

Verdict

pytest-asyncio 1.4.0 installed in 0.4 seconds and used 8 MB across 7 packages, with no audit findings in our sandbox, but its package metadata did not state a license. Use it for an asyncio-only pytest suite after pinning discovery and loop scopes; mismatched fixture and loop lifetimes are the main reason to leave it out.

We installed it

Lab card: what happened when we installed pytest-asyncioScreenshot of pytest-asyncio documentation
Install✓ · 0.4s7 packages on disk · 8 MB
Importimport pytest_asyncio in 0.84s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does pytest-asyncio install cleanly?

Yes. In a fresh container with an empty cache, pip install pytest-asyncio finished in 0.4s, leaving 7 packages and 8 MB on disk. pip-audit reported no known vulnerabilities.

What does pytest-asyncio need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import pytest_asyncio succeeded in 0.84s, and the package ships py.typed for type checkers.

pytest-asyncio or anyio: which should you use?

anyio: Pick its pytest plugin when identical tests must cover asyncio and Trio backends. pytest-asyncio 1.4.0 installed in 0.4 seconds and used 8 MB across 7 packages, with no audit findings in our sandbox, but its package metadata did not state a license.

When should you not use pytest-asyncio?

The application is written for Trio. pytest-trio knows nursery and Trio cancellation behavior that this plugin does not.

API stability3/5Markers and `pytest_asyncio.fixture` remain familiar, while loop customization has moved during the 1.x series. Version 1.4.0 deprecates `event_loop_policy` fixture overrides, introduces a named loop-factory hook, and requires pytest 8.4 or newer. Those changes follow Python's retirement of event-loop policies, but they still force projects with custom `conftest.py` files to migrate code and make both fixture and test loop scopes explicit.
Docs4/5The official manual covers strict and auto discovery, marker arguments, fixture decorators, loop scopes, configuration keys, debug mode, concepts, and migrations. Examples show session-scoped resources and the 1.4 loop-factory hook. The difficult part spans several concepts: pytest cache lifetime, collector hierarchy, event-loop lifetime, and plugin ownership all affect one test, so diagnosing a cross-loop failure often requires reading more than the page for its decorator.
Maintenance5/5The pytest-dev repository was pushed on 2026-08-21 and GitHub counts 50 open issues and pull requests. Version 1.4.0 shipped on 2026-05-26 with pytest 8.4 support, a replacement for policy overrides, clearer scope warnings, and cleanup fixes after `asyncio.run()` or `set_event_loop(None)`. The work tracks active changes in pytest and Python asyncio rather than merely updating packaging metadata.
Ecosystem5/5GitHub reports 1,657 stars, and pytest-asyncio plugs directly into pytest collection, fixtures, parametrization, assertions, and reports without another runner. Our install confirmed a typed, pure-Python distribution that imports on Python 3.12. That reach is specific to asyncio: Trio suites need pytest-trio, backend-neutral suites fit AnyIO, and auto mode can conflict when several async plugins are installed together.

Use it if

  • An asyncio suite should keep pytest assertions, parametrization, fixtures, and reporting.
  • Async resources need setup and teardown on an explicitly chosen loop lifetime.
  • Tests require either function isolation or a shared class, module, package, or session loop.
  • The same test must run against named event-loop factories such as uvloop through the 1.4 hook.
Skip it if

Setup reality

We installed pytest-asyncio 1.4.0 in a fresh Python 3.12 Bookworm container in 0.4 seconds. Seven packages occupied 8 MB, import pytest_asyncio completed in 0.84 seconds, and pip-audit reported zero known vulnerabilities. Metadata lists 8 direct dependencies and Python 3.10 or newer. The distribution is pure Python and contains py.typed. Its installed metadata did not identify a license, so consumers with automated license gates must resolve that unknown rather than borrowing a value from the repository page.

Installation registers the plugin with pytest. Strict mode requires @pytest.mark.asyncio on coroutine tests and @pytest_asyncio.fixture on async fixtures. Auto mode claims async tests and fixtures without those decorators, which is convenient until another async plugin also tries to own them. Set asyncio_mode, asyncio_default_test_loop_scope, and asyncio_default_fixture_loop_scope in checked-in pytest configuration so collection does not depend on a developer's local defaults.

Pytest cache scope and asyncio loop scope are independent controls. A session fixture created on a function loop outlives the loop and can later raise a closed-loop or different-loop error. Give an async fixture a loop_scope at least as wide as its cache scope, then keep dependent tests on a compatible loop. Function scope isolates loop state. A session loop reduces setup cost but also allows pending tasks and loop mutations to survive between tests.

Release 1.4.0 replaces policy-fixture overrides with pytest_asyncio_loop_factories. A mapping with several factories parametrizes matching coroutine tests; marker arguments select named entries. Calling asyncio.run() or asyncio.set_event_loop(None) inside a test can still confuse code that reads global loop state, although 1.4.0 repairs plugin cleanup around those calls. The plugin does not take over async methods on unittest.TestCase, so use unittest.IsolatedAsyncioTestCase for that style.

Patterns

Run one coroutine test mark-async-test

import pytest

@pytest.mark.asyncio
async def test_fetch(client):
    result = await client.fetch()
    assert result.status == 200

In strict mode, this marker gives the coroutine to pytest-asyncio. Without it, pytest may skip the function and only print a warning.

Claim async tests automatically configure-auto-mode

# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = 'auto'
asyncio_default_test_loop_scope = 'function'
asyncio_default_fixture_loop_scope = 'function'

Auto mode claims every async test and fixture. Install it only where another plugin will not compete for the same collected functions.

Open and close an async resource define-async-fixture

import pytest_asyncio

@pytest_asyncio.fixture
async def client():
    value = await connect()
    yield value
    await value.aclose()

Strict discovery recognizes `pytest_asyncio.fixture`, not a bare pytest async fixture. Cleanup after `yield` executes on this fixture's loop.

Match fixture and loop lifetime share-session-resource

@pytest_asyncio.fixture(scope='session', loop_scope='session')
async def pool():
    value = await create_pool()
    yield value
    await value.close()

A session fixture cannot safely depend on a function loop that closes after one test. Match both lifetimes for loop-bound clients and pools.

Run a module on one loop share-module-loop

import pytest

pytestmark = pytest.mark.asyncio(loop_scope='module')

async def test_write(db):
    await db.write('x')

Every marked coroutine in this module uses one loop. Unfinished tasks or altered loop state can therefore affect the next test.

Create several coroutine cases parametrize-async-test

@pytest.mark.asyncio
@pytest.mark.parametrize('value', [1, 2, 3])
async def test_valid(value):
    assert await validate(value)

Pytest creates 3 test items here. With function loop scope, each item also receives a new event loop.

Test with uvloop through the new hook register-loop-factory

# conftest.py
import uvloop

def pytest_asyncio_loop_factories(config, item):
    return {'uvloop': uvloop.new_event_loop}

Version 1.4 uses this hook instead of an `event_loop_policy` override. Returning multiple names parametrizes eligible tests across factories.

Choose one configured factory select-loop-factory

@pytest.mark.asyncio(loop_factories=['uvloop'])
async def test_transport():
    assert await check_transport()

`uvloop` must be a key returned by `pytest_asyncio_loop_factories`. An unknown name cannot create a loop for the test.

Run with asyncio diagnostics enable-asyncio-debug

# pytest.ini
[pytest]
asyncio_debug = true

Asyncio debug checks can reveal un-awaited coroutines and slow callbacks. They add overhead, so CI timing may differ from a normal run.

Use the standard unittest async class test-unittest-coroutine

import unittest

class TestApi(unittest.IsolatedAsyncioTestCase):
    async def test_get(self):
        self.assertEqual(await get_value(), 42)

pytest-asyncio excludes `unittest.TestCase` coroutine methods. `IsolatedAsyncioTestCase` creates and owns the loop for this class itself.

Alternatives

PackageRegistryPick it when
anyioPyPIPick its pytest plugin when identical tests must cover asyncio and Trio backends.
pytest-trioPyPIPick it for native Trio fixtures, nurseries, cancellation, and clock control.
pytest-tornasyncPyPIKeep it for an existing Tornado suite already written around that plugin.

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.