TestClient必须传入已初始化的FastAPI实例,不能传app()或字符串;测试函数须用def而非async def;json=参数自动序列化并设Content-Type;断言需同时校验status_code和响应内容。

TestClient 必须传入 FastAPI 实例,不能传 app() 或字符串
常见错误是写成 client = TestClient(app()) 或 TestClient("main:app"),这会直接报错 TypeError: 'FastAPI' object is not callable 或 AssertionError: app must be an ASGI application。TestClient 只接受已初始化的 FastAPI() 对象实例,不是调用结果,也不是模块路径。
正确做法是确保你在测试文件里已经 import 了应用实例,且它是一个 FastAPI 类型的对象:
from fastapi import FastAPI from fastapi.testclient import TestClient from main import app # ← 这个 app 是 FastAPI() 实例,不是函数或字符串 <p>client = TestClient(app) # ✅ 正确
- 如果 app 在
main.py中定义为app = FastAPI(),就从main导入 - 避免在测试中重复
app = FastAPI()—— 这会丢失路由、中间件、依赖注入等真实配置 - 不支持 uvicorn 启动参数(如
host/port),TestClient 完全绕过网络栈
测试函数必须用 def,不能用 async def
即使你的路由是 async def,测试函数本身必须是普通同步函数。因为 TestClient 内部封装的是 requests(同步),不是 httpx.AsyncClient。写成 async def test_xxx() 并用 await client.get(...) 会报错 RuntimeWarning: coroutine 'AsyncClient.get' was never awaited,而且 pytest 默认不执行 async 测试函数。
正确写法:
def test_read_main():
response = client.get("/") # ✅ 同步调用
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
- 所有
client.get()、client.post()等方法都是阻塞式调用,不用 await - 想测异步逻辑(比如数据库 await 操作)?得换
httpx.AsyncClient+pytest-asyncio,但那是另一套方案,不是TestClient的职责 - pytest 插件如
pytest-asyncio对TestClient无意义,反而可能干扰生命周期
传参方式和 requests 一致,但 json= 自动序列化
TestClient 继承自 requests.Session,所以查询参数、表单、JSON 负载的传法和 requests 完全一样,只是多了 json= 参数自动处理 Content-Type: application/json 和序列化。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
示例对比:
# 查询参数(?q=foo&offset=10)
response = client.get("/items", params={"q": "foo", "offset": 10})
<h1>JSON POST(自动加 headers + dumps)</h1><p>payload = {"name": "test", "price": 9.99}
response = client.post("/items", json=payload) # ✅ 不用手动 json.dumps()</p><h1>表单数据(application/x-www-form-urlencoded)</h1><p>response = client.post("/login", data={"username": "a", "password": "b"})</p><h1>带 header 的请求</h1><p>response = client.get("/protected", headers={"Authorization": "Bearer xyz"})
-
params=只用于 query string,不用于路径参数(路径参数直接写进 URL 字符串里,如"/users/123") -
json=会自动设Content-Type,不要同时传headers里再写这个头,否则可能冲突 - 如果后端用
Form依赖项,必须用data=;用Body或 Pydantic 模型,优先用json=
状态码和响应断言别只看 status_code
只写 assert response.status_code == 200 是危险的:它通过不代表业务逻辑正确。比如一个本该返回 404 的删除接口,因异常被吞成 200,测试就漏过了。
必须结合语义断言:
def test_delete_nonexistent_user():
response = client.delete("/users/999999")
assert response.status_code == 404 # ✅ 先看状态码
assert response.json()["detail"] == "User not found" # ✅ 再看错误消息是否匹配预期
- 对 2xx 响应,检查
response.json()结构是否符合 Pydanticresponse_model - 对 4xx/5xx,检查
response.json()["detail"]是否含预期错误描述(FastAPI 默认用这个 key) - 避免用
response.text做断言 —— 容易因空格、换行、编码差异失败;优先用.json()或.content(二进制) - 注意:未设置
response_model的路由,.json()返回原始 dict,但字段顺序不保证,别用==比较整个 dict,应逐 key 断言
测试真正难的部分不在写断言,而在于让每个测试干净独立 —— 比如数据库状态、缓存、全局变量。这些不会被 TestClient 隔离,得靠 fixture 或事务回滚控制,那已经是集成测试层级的事了。

















