fastapi: 测试:RuntimeError: Task <...> got Future <...> attached to a different loop
一,报错信息
E RuntimeError: Task <Task pending name='Task-6' coro=<_wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.finalizer.<locals>.async_finalizer()
running at /data/python/fastapi/demo1/venv/lib/python3.10/site-packages/pytest_asyncio/plugin.py:416>
cb=[_run_until_complete_cb() at /usr/local/soft/python3.10.19/lib/python3.10/asyncio/base_events.py:184]>
got Future <Future pending> attached to a different loop
/usr/local/soft/python3.10.19/lib/python3.10/asyncio/streams.py:501: RuntimeError
RuntimeError: Task <Task pending name='Task-8' coro=<AsyncConnection.close() running at /data/python/fastapi/demo1/venv/lib/python3.10/site-packages/sqlalchemy/ext/asyncio/engine.py:483>
cb=[shield.<locals>._inner_done_callback() at /usr/local/soft/python3.10.19/lib/python3.10/asyncio/tasks.py:847]>
got Future <Future pending> attached to a different loop
二,原因:
这个错误的含义:
RuntimeError: Task <...> got Future <...> attached to a different loop:
测试套件在运行期间,创建了多个不同的事件循环(Event Loop),
而你的数据库连接池(Connection Pool)或某些全局异步资源,在一个旧的 Loop 里创建,却试图在另一个新的 Loop 里被关闭(或调用)。
原因:
在 pytest-asyncio 的默认机制中:
scope="function" 的 Fixture 和测试用例:每次执行一个独立的测试函数,pytest 就会销毁当前的 Event Loop,然后为下一个测试函数创建一个崭新的 Event Loop。
scope="session" 的 Fixture(比如你用来创建表结构的 init_test_database):
它是在测试刚开始时,在一个旧的(例如 Session 级别的)Event Loop 里创建了 SQLAlchemy 的 Engine(引擎)或连接池。
当你的测试用例运行完毕,pytest 准备销毁环境并关闭数据库连接(触发 AsyncConnection.close() 或 Fixture 的 finalizer)时,当前活动的 Event Loop 已经变了。
SQLAlchemy 的底层连接由于绑定了之前的旧 Loop,当它在新 Loop 里尝试操作时,异步框架就会崩溃并抛出:attached to a different loop。
三,解决:
配置 pytest.ini 的 asyncio_default_fixture_loop_scope
如果你使用的是 pytest-asyncio 较新的版本(v0.23+),官方引入了一个更优雅的配置项。
你不需要在代码里重写 event_loop 了,直接去你项目的根目录,在 pytest.ini 文件中添加以下两行配置:
[pytest]
asyncio_mode = auto
# 核心配置:将异步 fixture 的默认 loop 作用域提升为 function 级别,或者保持统一设置为session
asyncio_default_fixture_loop_scope = session
浙公网安备 33010602011771号