I ran into a very similar, if not the same, problem. (Pytest failed with "loop closed" only if I ran all the tests). The solution for me was different, so I thought I'd post it.
At the top of my conftest.py file I had:
@pytest.fixture(params=["asyncio"], scope="session")
def anyio_backend(request):
return request.param
# AsyncClient comes from HTTPX and "app" is FastAPI
@pytest.fixture(scope="session")
async def client() -> AsyncIterator[AsyncClient]:
async with AsyncClient(app=app, base_url="http://testserver") as client:
yield client
If I ran tests in a single module at a time (or in VSCode's test plugin) everything went fine. But if I ran all my tests via pytest terminal command, everything would pass and then I would get... THIS:
__________________________________________________ ERROR at teardown of TestValidations.test_validate_url_with_malformed_or_altered_token[asyncio] ___________________________________________________
anyio_backend = 'asyncio', args = (), kwargs = {}, backend_name = 'asyncio', backend_options = {}, runner = <anyio._backends._asyncio.TestRunner object at 0x1049eaad0>
def wrapper(*args, anyio_backend, **kwargs): # type: ignore[no-untyped-def]
backend_name, backend_options = extract_backend_and_options(anyio_backend)
if has_backend_arg:
kwargs["anyio_backend"] = anyio_backend
with get_runner(backend_name, backend_options) as runner:
if isasyncgenfunction(func):
> yield from runner.run_asyncgen_fixture(func, kwargs)
../../Library/Caches/pypoetry/virtualenvs/ab-bJoznT_5-py3.11/lib/python3.11/site-packages/anyio/pytest_plugin.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../Library/Caches/pypoetry/virtualenvs/ab-bJoznT_5-py3.11/lib/python3.11/site-packages/anyio/_backends/_asyncio.py:2097: in run_asyncgen_fixture
self._loop.run_until_complete(fixture_task)
../../.pyenv/versions/3.11.4/lib/python3.11/asyncio/base_events.py:628: in run_until_complete
self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_UnixSelectorEventLoop running=False closed=True debug=False>
def _check_closed(self):
if self._closed:
> raise RuntimeError('Event loop is closed')
E RuntimeError: Event loop is closed
../../.pyenv/versions/3.11.4/lib/python3.11/asyncio/base_events.py:519: RuntimeError
Ugh.
Solution
The problem was rooted in the session-scoped client
. I changed it to "module" scope and all was fixed! No need to manually mess with event loops.
@pytest.fixture(params=["asyncio"], scope="session")
def anyio_backend(request):
return request.param
# AsyncClient comes from HTTPX and "app" is FastAPI
@pytest.fixture(scope="module")
async def client() -> AsyncIterator[AsyncClient]:
async with AsyncClient(app=app, base_url="http://testserver") as client:
yield client
I'm not sure if it's because I wasn't explicitly cleaning up after the yield
or perhaps HTTPX was closing the loop before anyio. (I'd welcome some insight from anyone more experienced than I.)
For anyone encountering this issue in the future, I'd recommend considering fixture scopes - perhaps it will save you from needing to play with event loops.