FastAPI 事件测试实战:用 TestClient 上下文管理器触发 lifespan 与 startup/shutdown 事件

FastAPI 事件测试实战:用 TestClient 上下文管理器触发 lifespan 与 startup/shutdown 事件 FastAPI 事件测试实战用 TestClient 上下文管理器触发 lifespan 与 startup/shutdown 事件【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi在 FastAPI 应用中lifespan上下文管理器和已弃用的startup/shutdown事件负责应用的启动与关闭逻辑例如加载全局数据、建立连接、注册资源等。但直接实例化TestClient(app)并不会执行这些事件导致依赖启动初始化状态的测试会意外失败。本文讲解如何把TestClient作为with上下文管理器使用让 lifespan 在测试中完整跑完启动 → 请求 → 清理三阶段并给出 lifespan 与 startup/shutdown 两种写法下可直接复制运行的测试示例。为什么 lifespan 事件需要显式触发FastAPI 允许你为应用定义生命周期逻辑现代写法是传入一个lifespan异步上下文管理器其yield之前执行启动逻辑yield之后执行清理逻辑旧写法是app.on_event(startup)/app.on_event(shutdown)装饰器现已标记为弃用deprecated。关键点在于TestClient对 lifespan 的执行采用惰性触发策略——只有当客户端进入上下文管理块with TestClient(app) as client:时才会模拟应用启动、执行 lifespan 的yield之前部分退出该with块时才会模拟应用终止、执行yield之后的清理部分。这与 Starlette 的测试客户端行为一致FastAPI 的测试客户端就是直接复用 Starlette 的实现见 fastapi/testclient.py其内容仅有一行from starlette.testclient import TestClient as TestClient。如果不使用with语句而是简单写client TestClient(app)那么 lifespan 根本不会执行任何依赖启动初始化数据的请求都会得到空数据或报错。使用 with 语句测试 lifespan官方教程示例 docs_src/app_testing/tutorial004_py310.py 演示了完整用法。先看应用定义部分from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.testclient import TestClient items {} asynccontextmanager async def lifespan(app: FastAPI): items[foo] {name: Fighters} items[bar] {name: Tenders} yield # clean up items items.clear() app FastAPI(lifespanlifespan) app.get(/items/{item_id}) async def read_items(item_id: str): return items[item_id]这里的lifespan是一个由asynccontextmanager装饰的异步上下文管理器yield之前往全局字典items中写入两条数据模拟启动时的资源初始化yield之后清空字典模拟关闭时的资源释放。FastAPI(lifespanlifespan)把它注册到应用上lifespan参数的官方文档说明见 fastapi/applications.py明确指出它用一个上下文管理器取代了startup和shutdown两个函数列表。然后是测试部分核心就是with TestClient(app) as client:这一行def test_read_items(): # Before the lifespan starts, items is still empty assert items {} with TestClient(app) as client: # Inside the with TestClient block, the lifespan starts and items added assert items {foo: {name: Fighters}, bar: {name: Tenders}} response client.get(/items/foo) assert response.status_code 200 assert response.json() {name: Fighters} # After the requests is done, the items are still there assert items {foo: {name: Fighters}, bar: {name: Tenders}} # The end of the with TestClient block simulates terminating the app, so # the lifespan ends and items are cleaned up assert items {}这段测试值得逐段拆解它验证了 lifespan 的完整时序进入with块之前items仍为空字典。此时 lifespan 尚未开始任何启动逻辑都未运行——这是不使用上下文管理器时最常见的测试陷阱。进入with块之后with TestClient(app) as client:语句执行完的瞬间lifespan 的启动部分已经执行完毕items中已有两条数据。这个断言证明了进入上下文即触发启动逻辑的行为。在块内发起请求client.get(/items/foo)返回 200 和预期 JSON且请求完成后items依然存在说明启动数据在整个请求生命周期内可用而不是用完即弃。退出with块之后items重新变回空字典。退出上下文块会模拟应用终止触发 lifespan 中yield之后的清理代码items.clear()。仓库中的测试验证这个教程示例本身就是被测对象。仓库的测试套件 tests/test_tutorial/test_testing/test_tutorial004.py 直接从教程模块导入并执行了上述测试函数from docs_src.app_testing.tutorial004_py310 import test_read_items def test_main(): test_read_items()这说明示例代码是可独立运行的 pytest 用例你可以把它复制到本地项目中原样执行而不必依赖文档渲染工具。测试已弃用的 startup/shutdown 事件如果你的项目仍在使用旧版on_event事件app.on_event(startup)与app.on_event(shutdown)测试方式完全相同把TestClient放入with块即可。官方示例 docs_src/app_testing/tutorial003_py310.pyfrom fastapi import FastAPI from fastapi.testclient import TestClient app FastAPI() items {} app.on_event(startup) async def startup_event(): items[foo] {name: Fighters} items[bar] {name: Tenders} app.get(/items/{item_id}) async def read_items(item_id: str): return items[item_id] def test_read_items(): with TestClient(app) as client: response client.get(/items/foo) assert response.status_code 200 assert response.json() {name: Fighters}与 lifespan 示例的区别有两点这里没有显式的清理逻辑原示例未定义shutdown事件因此测试退出with块时只会模拟应用终止没有数据断言上的变化启动逻辑被拆成独立的app.on_event(startup)装饰函数而不是一个围绕yield的上下文管理器。对应地仓库中也有专门的测试 tests/test_tutorial/test_testing/test_tutorial003.py 来执行这个用例。为什么应该优先使用 lifespan从源码看on_event在 fastapi/applications.py 中被显式标注为deprecated文档字符串直接写明 on_event is deprecated, use lifespan event handlers insteadFastAPI构造函数的on_startup/on_shutdown参数fastapi/applications.py的说明同样建议改用lifespanhandlers。lifespan 写法的优势在于启动与清理逻辑被收敛到同一个上下文管理器中yield前后代码紧邻资源获取与释放天然配对不易出现注册了启动却忘记注册清理的遗漏。FastAPI 更完整的生命周期事件说明包括lifespan的推荐写法与替代方案的细节见 docs/en/docs/advanced/events.md德语版文档见 docs/de/docs/advanced/events.md。实践要点任何依赖启动初始化状态的测试必须使用with TestClient(app) as client:而不是client TestClient(app)两者对 lifespan 的触发行为完全不同。断言放在正确的阶段with块之前断言未初始化状态块内断言已初始化状态和请求结果块外断言已清理状态这样才能完整覆盖启动、服务、清理三个阶段的时序。每个with块对应一次完整的应用生命周期多次进入with块会多次执行 lifespan测试中若需在共享 fixture 里启动应用建议把TestClient的创建和关闭都放进with保证清理逻辑必然执行。新代码统一使用lifespan避免引入新的on_event弃用告警存量startup/shutdown事件的迁移方式是把启动逻辑移到yield前、清理逻辑移到yield后。FastAPI 的TestClient直接转发自 Starlettefastapi/testclient.py因此 Starlette 文档中关于 Running lifespan in tests 的上下文管理器语义在这里同样适用。总结测试 FastAPI 事件机制的核心只有一条规则把TestClient当作上下文管理器使用。进入with块触发 lifespan 启动逻辑退出块触发清理逻辑已弃用的startup/shutdown事件遵循同样的触发规则。配套的教程示例 docs_src/app_testing/tutorial004_py310.py 与 docs_src/app_testing/tutorial003_py310.py 均为可直接运行的 pytest 用例配合 tests/test_tutorial/test_testing/ 下的对应测试文件可作为你项目中事件测试的模板直接参考。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考