# Python : Testing : pytest : async
This page is about the [pytest-async](https://pypi.org/project/pytest-async/) package. However, I've concluded it's better to use the pytest plugin built-in to the [anyio](https://pypi.org/project/anyio/) package:
- the `anyio` package is already required by `httpx` and `starlette` (which is used by `fastapi` and `ariadne`), so why bother adding `pytest-async`
- the `anyio` pytest plugin has a better internal architecture than `pytest-async`, as explained in the `anyio` docs:
*"The `pytest-asyncio` library only works with asyncio code. Like the AnyIO pytest plugin, it can be made to support higher order fixtures (by specifying a higher order `event_loop` fixture). However, it runs the setup and teardown phases of each async fixture in a new async task per operation, making context variable propagation impossible and preventing task groups and cancel scopes from functioning properly."*
- continuing on the topic of internal architecture, I couldn't get `pytest-async` to work with `tortoise-orm` because tortoise stores connections in ContextVars, but it worked fine with `anyio`
# pytest-async
\[ [pypi](https://pypi.org/project/pytest-asyncio/) | [src](https://github.com/pytest-dev/pytest-asyncio/) | [docs](https://pytest-asyncio.readthedocs.io/en/latest/) ]
Provides support for coroutines as test functions.
Defaults to "strict mode" in which pytest-asyncio will only run tests with the `asyncio` marker (and fixtures with `@pytest_asyncio.fixture` decorator). This allows co-existance with other async runners.
If you're not using any other async runners, "auto mode" is recommended, in which pytest-asyncio will take ownership of all async tests and fixtures without need for individual decoration.
## Cheatsheet
```python
## Marking individual functions:
@pytest.mark.asyncio
async def test_foo():
assert await lib.do_something() == "expected result"
@pytest.mark.asyncio( loop_scope="function*|class|module|package|session" )
async def test_bar():
...
## Marking a whole class:
@pytest.mark.asyncio
class TestClass:
...
## Marking a whole module:
pytestmark = [ pytest.mark.asyncio, ... ]
## Marking a whole package:
# __init__.py
pytestmark = [ pytest.mark.asyncio, ... ]
## Marking the entire test suite:
$ pytest --asyncio-mode=auto
# pyproject.toml
asyncio_mode = "auto"
```
**Fixtures**
```python
import pytest_asyncio
@pytest_asyncio.fixture( ... ) # Same args as pytest.fixture, plus...
async def foo(): # loop_scope -> must be larger or equal to scope
...
# in auto mode
@pytest.fixture( ... ) # If you need to set loop_scope, still need to use pytest_asyncio.fixture instead.
async def foo():
...
```
## Details
#### Loop Scope
Tests run in the event loop for their configured loop scope. Loop scope defaults to "function" which gives tests the highest level of isolation from each other.
> [!TIP]
> It’s highly recommended for neighboring tests (in same class or module) to use the same scope type. Having one test with scope "function" next to another with scope "package" is confusing to reason about.
> [!NOTE] To Run All Tests in the Same Loop
> https://pytest-asyncio.readthedocs.io/en/latest/how-to-guides/run_session_tests_in_same_loop.html
#### Config
```toml
asyncio_default_fixture_loop_scope # Determines the default loop_ _scope of asynchronous fixtures.
# If not set, will default to the fixture's scope.
# In a future version, the value will default to "function" when unset.
# Values: function | class | module | package | session
asyncio_mode = "strict" # It's recommended to set this to "auto".
```
#### Built-in Fixtures
```
event_loop # Creates a new asyncio event loop based on the current event loop policy.
# If you need to change the type of the event loop, set a custom event loop policy.
event_loop_policy # default -> asyncio.get_event_loop_policy()
```
> [!TIP] To Use `uvloop`
> ```python
> @pytest.fixture( scope="session" )
> def event_loop_policy():
> return uvloop.EventLoopPolicy()
> ```