Testing with AnyIO¶
AnyIO provides built-in support for testing your library or application in the form of a pytest plugin.
Creating asynchronous tests¶
To mark a coroutine function to be run via anyio.run()
, simply add the @pytest.mark.anyio
decorator:
import pytest
@pytest.mark.anyio
async def test_something():
pass
Asynchronous fixtures¶
The plugin also supports coroutine functions as fixtures, for the purpose of setting up and tearing down asynchronous services used for tests:
import pytest
@pytest.fixture
async def server():
server = await setup_server()
yield server
await server.shutdown()
@pytest.mark.anyio
async def test_server(server):
result = await server.do_something()
assert result == 'foo'
Any coroutine fixture that is activated by a test marked with @pytest.mark.anyio
will be run
with the same backend as the test itself. Both plain coroutine functions and asynchronous generator
functions are supported in the same manner as pytest itself does with regular functions and
generator functions.
Note
If you need Python 3.5 compatibility, please use the async_generator library to replace the async generator syntax that was introduced in Python 3.6.
Specifying the backend to run on¶
By default, all tests are run against the default backend (asyncio). The pytest plugin provides a
command line switch (--anyio-backends
) for selecting which backend(s) to run your tests
against. By specifying a special value, all
, it will run against all available backends.
For example, to run your test suite against the curio and trio backends:
pytest --anyio-backends=curio,trio
Behind the scenes, any function that uses the @pytest.mark.anyio
marker gets parametrized by
the plugin to use the anyio_backend
fixture. One alternative is to do this parametrization on
your own:
@pytest.mark.parametrize('anyio_backend', ['asyncio'])
async def test_on_asyncio_only(anyio_backend):
...
Or you can write a simple fixture by the same name that provides the back-end name:
@pytest.fixture(params=['asyncio'])
def anyio_backend(request):
return request.param
If you want to specify different options for the selected backend, you can do so by passing a tuple
of (backend name, options dict). The latter is passed as keyword arguments to anyio.run()
:
@pytest.fixture(params=[
pytest.param(('asyncio', {'use_uvloop': True}), id='asyncio+uvloop'),
pytest.param(('asyncio', {'use_uvloop': False}), id='asyncio'),
pytest.param('curio'),
pytest.param(('trio', {'restrict_keyboard_interrupt_to_checkpoints': True}), id='trio')
])
def anyio_backend(request):
return request.param
Because the anyio_backend
fixture can return either a string or a tuple, there are two
additional fixtures (which themselves depend on the anyio_backend
fixture) provided for your
convenience:
anyio_backend_name
: the name of the backend (e.g.asyncio
)anyio_backend_options
: the dictionary of option keywords used to run the backend
Using AnyIO from regular tests¶
In rare cases, you may need to have tests that run against whatever backends you have chosen to
work with. For this, you can add the anyio_backend
parameter to your test. It will be filled
in with the name of each of the selected backends in turn:
def test_something(anyio_backend):
assert anyio_backend in ('asyncio', 'curio', 'trio')