pytest-dev / pytest-asyncio

Asyncio support for pytest
https://pytest-asyncio.readthedocs.io
Apache License 2.0
1.43k stars 152 forks source link

The event loop of Motor unexpected closed when I using fastapi.testclient.TestClient to test my api #844

Closed XYCode-Kerman closed 5 months ago

XYCode-Kerman commented 6 months ago

Traceback

    async def test_create_userpool():
        from main import app
        with TestClient(app) as client:
>           resp = client.post('/management/userpool/', headers={
                'su-token': SUPER_USER_TOKEN
            }, json=TEST_USERPOOL)

tests/test_management/test_userpool.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/testclient.py:633: in post
    return super().post(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:1145: in post
    return self.request(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/testclient.py:516: in request
    return super().request(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:827: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/httpx/_client.py:1015: in _send_single_request
    response = transport.handle_request(request)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/testclient.py:398: in handle_request
    raise exc
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/testclient.py:395: in handle_request
    portal.call(self.app, scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/anyio/from_thread.py:288: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
/usr/lib/python3.12/concurrent/futures/_base.py:456: in result
    return self.__get_result()
/usr/lib/python3.12/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/anyio/from_thread.py:217: in _call_func
    retval = await retval_or_awaitable
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/fastapi/applications.py:1054: in __call__
    await super().__call__(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/applications.py:123: in __call__
    await self.middleware_stack(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/middleware/exceptions.py:65: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py:64: in wrapped_app
    raise exc
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    await app(scope, receive, sender)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/routing.py:756: in __call__
    await self.middleware_stack(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/routing.py:776: in app
    await route.handle(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/routing.py:297: in handle
    await self.app(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/routing.py:77: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py:64: in wrapped_app
    raise exc
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    await app(scope, receive, sender)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/starlette/routing.py:72: in app
    response = await func(request)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/fastapi/routing.py:278: in app
    raw_response = await run_endpoint_function(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/fastapi/routing.py:191: in run_endpoint_function
    return await dependant.call(**values)
routers/management/userpool.py:40: in create_user_pool
    return await engine.save(userpool)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/odmantic/engine.py:577: in save
    async with await self.client.start_session() as local_session:
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/motor/frameworks/asyncio/__init__.py:148: in _wrapper
    result = await f(self, *args, **kwargs)
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/motor/metaprogramming.py:75: in method
    return framework.run_on_executor(
/home/xycode/.cache/pypoetry/virtualenvs/authpi-P9W-ssYy-py3.12/lib/python3.12/site-packages/motor/frameworks/asyncio/__init__.py:85: in run_on_executor
    return loop.run_in_executor(_EXECUTOR, functools.partial(fn, *args, **kwargs))
/usr/lib/python3.12/asyncio/base_events.py:850: in run_in_executor
    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

/usr/lib/python3.12/asyncio/base_events.py:541: RuntimeError

---------- coverage: platform linux, python 3.12.3-final-0 -----------
Coverage HTML written to dir htmlcov

Code

All code is fixed on https://github.com/XYCode-Kerman/AuthPI/tree/feature/test commit 8b071329aae577a0caa30bf29fccf01cd7d6f85f.

Reproduce the error via command pytest --cov --cov-report=html -v -s --asyncio-mode=auto.

graingert commented 6 months ago

you're using the synchronous test client with an async test framework, and you need to mount your database to your app using lifespan context

fixed here: https://github.com/XYCode-Kerman/AuthPI/pull/1/files

================================================================================ test session starts =================================================================================
platform linux -- Python 3.12.3+, pytest-8.2.1, pluggy-1.5.0
rootdir: /home/graingert/projects/AuthPI
configfile: pyproject.toml
plugins: anyio-4.3.0, cov-5.0.0, asyncio-0.21.2
asyncio: mode=Mode.STRICT
collected 3 items                                                                                                                                                                    

tests/test_management/test_userpool.py ..                                                                                                                                      [ 66%]
tests/test_utils.py .                                                                                                                                                          [100%]

================================================================================= 3 passed in 0.59s ==================================================================================

I don't think this is a bug in pytest-asyncio so I think this issue should be closed