如何在FastAPI/Starlette测试客户端中设置Request对象的state属性?
解决FastAPI测试中request.state为空的问题
问题根源
你的端点依赖request.state.token,但这个值通常是由中间件在请求进入端点前从Authorization头提取并设置的。测试时你虽然传了Authorization头,但测试用的TestClient对应的App实例没有加载这个处理token的中间件,导致request.state为空。
解决方案
方案1:确保测试App加载了真实的认证中间件
如果你的项目中已经有处理Authorization头并设置request.state.token的中间件(比如在main.py中添加的),测试时必须基于这个完整的App创建TestClient,而不是空的App。
示例:
# 测试文件中导入带中间件的App from main import app from starlette.testclient import TestClient # 创建TestClient时使用完整的App my_test_client = TestClient(app) def test_get_data(my_test_client: TestClient, api_url: str, api_token: str) -> None: params = { "offset": 0, "limit": 20, } # 注意:如果中间件要求Authorization头是"Bearer {token}"格式,要确保api_token符合格式 response = my_test_client.get(url=api_url, headers={"Authorization": f"Bearer {api_token}"}, params=params) assert response.status_code == HTTP_200_OK
方案2:测试时添加临时中间件
如果不想依赖真实中间件,可以在测试函数中临时添加一个中间件,模拟token提取逻辑:
from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request def test_get_data(my_test_client: TestClient, api_url: str, api_token: str) -> None: # 定义临时中间件:从Authorization头提取token并设置到request.state async def temp_auth_middleware(request: Request, call_next): auth_header = request.headers.get("Authorization") if auth_header: # 根据实际格式处理,这里假设api_token是完整的Bearer令牌 request.state.token = auth_header # 如果需要拆分Bearer前缀:_, token = auth_header.split(); request.state.token = token response = await call_next(request) return response # 将临时中间件添加到测试App my_test_client.app.add_middleware(BaseHTTPMiddleware, dispatch=temp_auth_middleware) params = { "offset": 0, "limit": 20, } response = my_test_client.get(url=api_url, headers={"Authorization": api_token}, params=params) assert response.status_code == HTTP_200_OK
方案3:用依赖覆盖简化测试
把提取token的逻辑抽成独立依赖,测试时直接覆盖这个依赖返回测试用token,无需处理request.state:
第一步:改造端点代码,抽离token依赖
from fastapi import Depends, Request # 抽离token提取逻辑为依赖 def get_request_token(request: Request): return request.state.token @router.get("/", response_model=DataOut) def get_documents( query_params: QueryParams = Depends(), token: str = Depends(get_request_token) # 注入token依赖 ) -> DataOut: return DocumentsOut.parse_obj( data_api_client.get( "/data/", token, params=query_params.dict(by_alias=True), ).json() )
第二步:测试中覆盖依赖
def test_get_data(my_test_client: TestClient, api_url: str, api_token: str) -> None: # 覆盖get_request_token依赖,直接返回测试token def override_token(): return api_token my_test_client.app.dependency_overrides[get_request_token] = override_token params = { "offset": 0, "limit": 20, } # 此时无需传Authorization头,依赖直接返回测试token response = my_test_client.get(url=api_url, params=params) assert response.status_code == HTTP_200_OK # 测试后清除依赖覆盖,避免影响其他测试 my_test_client.app.dependency_overrides.clear()
内容的提问来源于stack exchange,提问作者Okroshiashvili
相关产品推荐
相关产品推荐

