pytest-asyncio

repository·main·Indexed 23 days ago

https://github.com/pytest-dev/pytest-asyncio

A pytest plugin that enables testing of asynchronous code by providing support for coroutines as test functions. It allows the use of `await` within tests and provides configurable event loop scopes (Session, Package, Module, Class, or Function) via the `loop_scope` argument. The plugin supports two discovery modes: 'strict' for explicitly marked tests and fixtures, and 'auto' for automatic discovery of async items. It also provides the `pytest_asyncio_loop_factories` hook for implementing custom event loop factories, such as uvloop.

Tokens
5.7K
Snippets
10
Records
48
Agent score
81%

What's inside pytest-asyncio

  1. Understand test execution and concurrency in pytest-asyncio

    main

    pytest-asyncio runs asynchronous tests sequentially, mirroring the behavior of standard synchronous pytest runs. Each async test is executed within its assigned event loop.

    Sequential execution is used to maintain test isolation and prevent race conditions or side effects that would occur if tests were run concurrently, which would make results unreliable and difficult to debug.

  2. Configure loop_scope for tests and fixtures

    main

    To replace the deprecated event_loop fixture, you must explicitly define the loop scope for your asynchronous tests and fixtures. This ensures the event loop lifecycle matches your requirements.

    • For asynchronous tests: Use pytest.mark.asyncio(loop_scope="...").
    • For asynchronous fixtures: Use @pytest_asyncio.fixture(loop_scope="...").
    • Global configuration: Use the asyncio_default_fixture_loop_scope setting to define a default scope for all fixtures.
  3. Limitations with unittest subclasses

    main
    Note that pytest-asyncio does not support test classes that subclass the standard unittest library. If you need to test asynchronous code within a class-based structure, use unittest.IsolatedAsyncioTestCase or an async framework like asynctest instead.
  4. How asyncio event loop scopes work

    main

    pytest-asyncio provides one asyncio event loop for each pytest collector (Session, Package, Module, Class, or Function).

    By default, tests run in the event loop provided by the Function collector, which offers the highest level of isolation. However, you can configure tests to share an event loop from a common ancestor collector by passing the loop_scope keyword argument to the @pytest.mark.asyncio marker.

    Best Practice: It is highly recommended that neighboring tests (e.g., all tests within the same class or module) use the same event loop scope to keep the test code easy to follow.

  5. Migrate from pytest-asyncio v0.21

    main

    When migrating from version 0.21, you must update how the event_loop fixture is handled. The event_loop fixture is being deprecated in favor of managing loop scopes via loop_scope and using asyncio.get_running_loop().

    Step 1: Update Fixture and Test Implementations

    1. Clean up custom event loop fixtures: If you re-implement the event_loop fixture, ensure it only creates a new asyncio event loop, yields it, and closes it.
    2. Convert synchronous to asynchronous:
      • Convert synchronous test cases that request the event_loop fixture into asynchronous test cases.
      • Convert synchronous fixtures that request the event_loop fixture into asynchronous fixtures.
    3. Remove event_loop arguments:
      • For asynchronous test cases, remove the event_loop argument and use event_loop = asyncio.get_running_loop() instead.
      • For asynchronous fixtures, remove the event_loop argument and use event_loop = asyncio.get_running_loop() instead.

    Step 2: Configure Loop Scopes

    After removing your custom event_loop fixture, you must ensure your tests and fixtures use the correct loop scope to match the old fixture's scope.

    1. Match scopes: For every test or fixture affected by the old event_loop fixture, configure the loop_scope to match the original scope.
      • For tests: Use the pytest.mark.asyncio(loop_scope="...") marker.
      • For fixtures: Use the @pytest_asyncio.fixture(loop_scope="...") decorator.
      • Global default: Alternatively, set the default loop scope for all fixtures using the asyncio_default_fixture_loop_scope configuration option.
    2. Remove the old fixture: Once scopes are correctly configured, delete your re-implemented event_loop fixture.

    Step 3: Silence Deprecation Warnings

    If you have not yet configured asyncio_default_fixture_loop_scope, set it to function to silence deprecation warnings.

  6. Change the default event loop scope for all fixtures

    main

    By default, asynchronous fixtures in pytest-asyncio use a specific event loop scope. You can change this default scope for all fixtures globally using the asyncio_default_fixture_loop_scope configuration option. This is useful if you want all async fixtures to run in a session scoped loop instead of the default.

    Valid scope values include session and others defined in the project configuration documentation.

    ### pytest.ini
    ```ini
    [pytest]
    asyncio_default_fixture_loop_scope = session

    pyproject.toml

    [tool.pytest.ini_options]
    asyncio_default_fixture_loop_scope = "session"

    setup.cfg

    [tool:pytest]
    asyncio_default_fixture_loop_scope = session
  7. How to test with different event loops

    main

    To run async tests with different event loops, you can parametrize the event_loop_policy fixture. Note that parametrizing this fixture at the top level will cause all async tests to run multiple times (once for each parameter provided).

    To limit the impact of this parametrization, you can set the scope of the fixture to package, module, or class. This ensures only a specific subset of tests are executed with the different event loops.

    .. warning:: Overriding the event_loop_policy fixture is deprecated and will be removed in a future version of pytest-asyncio. It is recommended to use the pytest_asyncio_loop_factories hook instead.