pytest-asyncio
pytest-asyncio copied to clipboard
`Event loop is closed` when using `pytester` and `pytest_asyncio.fixture(autouse=True)`
If you have an async fixture with a teardown and autouse=True and you have a simple non-async test with a pytester plugin, then that fixture is always added as a finalizer.
Basically, if you have the following:
# tests/conftest.py
@pytest.fixture(autouse=True)
async def teardown(request):
try:
yield
finally:
pass
# tests/test_fixtures.py
def test_fixture_setup(pytester):
# create a temporary conftest.py file
pytester.makeconftest("""
pytest_plugins = ["tests.conftest"]
""")
# create a temporary pytest test file
pytester.makepyfile("""
def test_setup():
assert True
""")
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)
Then, the test fails on a teardown with the following error:
ERROR tests/test_fixtures.py::test_fixture_setup - RuntimeError: Event loop is closed
I've checked it on pytest-asyncio==0.16.0 and pytest>=0.17.0. However, prior v0.17.0 there was a workaround - you could redefine an event_loop in a temporary pytest test file without closing the loop. Since, v0.17.0 this is no longer possible.
In v0.17.0 the only workaround is to override this async fixture itself in temporary pytest test file.
I made a minimal reproducible example, you can find it here:
https://github.com/unmade/pytester-pytest-asyncio-bug
Hi all, I get similar issues on my tests. @asvetlov Any ideas? I guess this is linked to https://github.com/pytest-dev/pytest-asyncio/commit/d8efa640f0aa1ba8856b908ba486150588018209.
Thanks for the report! I can reproduce the issue with your example.
My current understanding is that the programmatic pytest run using pytester.runpytest causes the current loop provided by the event_loop fixture to close. In other words, pytester.runpytest is not a hermetic environment and affects the event_loop fixture of the test using pytester. Please correct me, if I'm wrong.
Modifying the event_loop fixture scope will not change anything, probably because pytester.runpytest represents a full test session.
The example repository presents two workarounds. One workaround is to redefine the event_loop fixture inside pytester.makepyfile and avoid closing the event loop. This workaround no longer works as of pytest-asyncio v0.18.
The second workaround involves redefining the teardownfixture in pytester.makepyfile so that it is no longer an async generator. That way, the fixture does not require finalization and will not trigger the error.
Both are workarounds, so I don't think it makes sense to revert to an earlier version of pytest-asyncio.
This is a tricky one to solve… Ideally, we would like pytester runs to have their own instances of event loop. If we had switched to aioloop-proxy (#235) we could easily spawn a "nested loop" for these type of tests. At the moment, I think this is the go-to solution.
An alternative is to whip up some custom logic specifically for tests requesting the pytester fixture. I have the feeling this will open up a can of worms, though, and cause more harm than it will do good.
I'm sorry that I don't have any immediate fix for this. At the moment, I'd say this issue is blocked by #235. If you have any suggestions, I'd be happy to hear them.
@unmade Does it solve the issue if you use pytester.runpytest_subprocess rather than pytester.runpytest?
I can no longer reproduce the issue using the code in the linked repository with pytest-asyncio 0.19.0. I checked CPython 3.7 up to v3.10. Please reopen if you happen to run into this problem again.
How to simplify this in async test? Such as
def test_get(pytester):
pytester.makepyfile(
"""\
async def test_get():
await get()
"""
)
pytester.runpytest_subprocess("--asyncio-mode=auto")
@unmade Does it solve the issue if you use pytester.runpytest_subprocess rather than
pytester.runpytest?
@crypto-only You'd only use pytester if you want to run pytest inside a test. This can be handy when you write a pytest plugin and want to test different configurations of the plugin.
Your example can be simplified by setting asyncio_mode = auto in pytest.ini and simply writing:
async def test_get():
await get()
There is no need to use pytester in your example.