Package: alt-pytest-asyncio
This plugin allows you to have async pytest fixtures and tests.This plugin only supports python 3.6 and above.The code here is influenced by pytest-asyncio but with some differences:Error tracebacks from are from your tests, rather than asyncio internalsThere is only one loop for all of the testsYou can manage the lifecycle of the loop yourself outside of pytest by using
this plugin with your own loopNo need to explicitly mark your tests as async. (pytest-asyncio requires you
mark your async tests because it also supports other event loops like curio
and trio)Like pytest-asyncio it supports async tests, coroutine fixtures and async
generator fixtures.Changelog0.7.2 - 1 October 2023Timeouts don’t take affect if the debugger is active0.7.1 - 23 June 2023No functional changes, only fixing how hatchling understands the
license field in the pyproject.toml with thanks to @piotrm-nvidia0.7.0 - 12 April 2023Changed the pytest dependency to be greater than pytest version 7Using isort nowWent from setuptools to hatchCI now runs against python 3.110.6.0 - 23 October 2021Fix bug where it was possible for an async generator fixture to
be cleaned up even if it was never started.This library is now 3.7+ onlyAdded an equivalentshutdown_asyncgento the OverrideLoop helper0.5.4 - 26 January 2021Added a--default-async-timeoutoption from the CLI. With many thanks
to @andredias.Renamed existing pytest.ini option fromdefault_alt_async_timeoutto
bedefault_async_timeout.0.5.3 - 25 July 2020Make sure a KeyboardInterrupt on running tests still shows errors from
failed tests0.5.2 - 6 February 2020Added ability to make a different event loop for some tests0.5.1 - 15 December 2019Added an ini optiondefault_alt_async_timeoutfor the default async
timeout for fixtures and tests. The default is now 5 seconds. So say
you wanted the default to be 3.5 seconds, you would setdefault_alt_async_timeoutto be 3.50.5 - 16 August 2019I made this functionality in a work project where I needed to run
pytest.main from an existing event loop. I decided to make this it’s
own module so I can have tests for this code.Running from your own event loopIf you want to run pytest.main from with an existing event loop then you can
do something like:fromalt_pytest_asyncio.pluginimportAltPytestAsyncioPlugin,run_coro_as_mainimportnest_asyncioimportasyncioimportpytestasyncdefmy_tests():awaitdo_some_setup_before_pytest()plugins=[AltPytestAsyncioPlugin(loop)]try:code=pytest.main([],plugins=plugins)finally:# Note that alt_pytest_asyncio will make sure all your async tests# have been finalized by this point, even if you KeyboardInterrupt# the pytest.mainawaitdo_any_teardown_after_pytest()ifcode!=0:raiseException(repr(code))if__name__=='__main__':# Nest asyncio is required so that we can do run_until_complete in an# existing event loop - https://github.com/erdewit/nest_asyncioloop=asyncio.get_event_loop()nest_asyncio.apply(loop)run_coro_as_main(loop,my_tests())Note that if you don’t need to run pytest from an existing event loop, you don’t
need to do anything other than have alt_pytest_asyncio installed in your
environment and you’ll be able to just use async keywords on your fixtures and
tests.Timeoutsalt_pytest_asyncio registers apytest.mark.async_timeout(seconds)mark which
you can use to set a timeout for your test.For example:importpytest@pytest.mark.async_timeout(10)asyncdeftest_something():awaitsomething_that_may_take_a_while()This test will be cancelled after 10 seconds and raise an assertion error saying
the test took too long and the file and line number where the test is.You can also use the async_timeout mark on coroutine fixtures:importpytest@pytest.fixture()@pytest.mark.async_timeout(0.5)asyncdefmy_amazing_fixture():awaitasyncio.sleep(1)return1And you can have a timeout on generator fixtures:importpytest@pytest.fixture()@pytest.mark.async_timeout(0.5)asyncdefmy_amazing_fixture():try:awaitasyncio.sleep(1)yield1finally:awaitasyncio.sleep(1)Note that for generator fixtures, the timeout is applied in whole to both the
setup and finalization of the fixture. As in the real timeout for the entire
fixture is essentially double the single timeout specified.The default timeout is 5 seconds. You can change this default by setting thedefault_async_timeoutoption to the number of seconds you want.This setting is also available from the CLI using the--default-async-timeoutoption.Note that if the timeout fires whilst you have the debugger active then the timeout
will not cancel the current test. This is determined by checking ifsys.gettrace()returns a non-None value.Overriding the loopSometimes it may be necessary to close the current loop in a test. For this to
not then break the rest of your tests, you will need to set a new event loop for
your test and then restore the old loop afterwards.For this, we have a context manager that will install a new asyncio loop and
then restore the original loop on exit.Usage looks like:from alt_pytest_asyncio.plugin import OverrideLoop

class TestThing:
    @pytest.fixture(autouse=True)
    def custom_loop(self):
        with OverrideLoop() as custom_loop:
            yield custom_loop

    def test_thing(self, custom_loop):
        custom_loop.run_until_complete(my_thing())By putting the loop into an autouse fixture, all fixtures used by the test
will have the custom loop. If you want to include module level fixtures too
then use the OverrideLoop in a module level fixture too.OverrideLoop takes in anew_loopboolean that will make it so no new
loop is set and asyncio is left with no default loop.The new loop itself (or None if new_loop is False) can be found in theloopattribute of the object yielded by the context manager.Therun_until_completeon thecustom_loopin the above example will
do arun_until_completeon the new loop, but in a way that means you
won’t getunhandled exception during shutdownerrors when the context
manager closes the new loop.When the context manager exits and closes the new loop, it will first cancel
all tasks to ensure finally blocks are run.