如何在pytest-twisted装饰的测试函数中正确使用pytest-asyncio的aiohttp_client fixture?
如何在pytest-twisted装饰的测试函数中正确使用pytest-asyncio的aiohttp_client fixture?
问题背景
你遇到的问题本质是Twisted的AsyncioReactor和pytest-asyncio维护的事件循环没有共享,导致两者的循环生命周期不一致,最终触发了RuntimeError: Event loop is closed错误。从你提供的最小复现代码和配置来看,核心冲突点在于:测试函数用@pytest_twisted.ensureDeferred装饰(依赖Twisted的反应器循环),但aiohttp_client fixture默认使用function级别的独立循环,两者循环实例不统一,最终导致循环被提前关闭。
解决方案
核心思路是让Twisted和aiohttp强制使用同一个全局共享的事件循环,并且统一循环的生命周期为整个测试会话。具体步骤如下:
1. 调整pytest配置
修改你的pyproject.toml(或pytest.ini),把循环范围设为session,确保整个测试过程中只有一个循环实例:
[tool.pytest.ini_options] addopts = [ "--reactor=asyncio", ] asyncio_mode = "strict" asyncio_default_fixture_loop_scope = "session"
2. 编写全局绑定反应器的Fixture
写一个自动执行的session级Fixture,把Twisted的AsyncioReactor绑定到pytest-asyncio提供的全局循环上,避免Twisted自行创建新循环:
import pytest from twisted.internet import asyncioreactor @pytest.fixture(scope="session", autouse=True) def setup_shared_reactor(event_loop): # 将Twisted反应器绑定到pytest-asyncio的session级循环 asyncioreactor.install(event_loop) reactor = asyncioreactor.AsyncioSelectorReactor(event_loop) # 测试会话结束后停止反应器 yield reactor reactor.stop()
3. 测试代码保持原有结构即可
你的最小测试代码不需要大改,现在可以正常运行:
from pytest_twisted import ensureDeferred from aiohttp import web @ensureDeferred async def test_minimal(aiohttp_client): app = web.Application() client = await aiohttp_client(app) # 示例测试逻辑:请求根路径,预期404(因为未配置路由) resp = await client.get('/') assert resp.status == 404
为什么这样能解决问题?
- 共享循环:把循环范围设为
session后,pytest-asyncio会在整个测试会话中维护同一个事件循环,不会在每个测试结束后就关闭它 - 绑定反应器:手动让Twisted的AsyncioReactor使用这个共享循环,避免了Twisted创建独立循环导致的冲突
- 生命周期统一:反应器和循环的生命周期都绑定到测试会话,不会出现循环提前关闭的情况
额外注意事项
- 不要在测试或Fixture中手动创建/关闭事件循环,完全交给pytest-asyncio管理
- 如果你的自定义Fixture依赖aiohttp或Twisted,记得把它们的scope也设为
session(如果业务逻辑允许),避免重复初始化 - 如果之前尝试过
asyncio_mode="auto",其实也可以使用,但strict模式更严谨,能帮你提前发现循环使用的问题
备注:内容来源于stack exchange,提问作者wRAR
相关产品推荐
相关产品推荐

