为何被补丁的admin_only函数未返回True?排查及修复方案
FastAPI依赖补丁未生效的原因及修复方案
我在src/routes/get_orders.py中导入了src.lib.authorization路径下的admin_only函数,并将其作为FastAPI路由的依赖项。在test_get_orders.py中尝试用unittest.mock.patch对该函数打补丁,让它返回True,但运行pytest时补丁未生效,程序仍然执行原函数的逻辑。
get_orders.py
# get_orders.py import boto3 import os from fastapi import APIRouter, Depends from aws_lambda_powertools import Logger from fastapi.exceptions import HTTPException from src.models import Order from src.lib.dynamodb import DynamoConnection from src.lib.response import fastapi_gateway_response from src.lib.authorization import admin_only logger = Logger() router = APIRouter() orders_table = DynamoConnection( os.environ.get("DYNAMODB_REGION", "us-east-1"), os.environ.get("DYNAMODB_ENDPOINT_URL", None), os.environ.get("DYNAMODB_ORDERS_TABLE_NAME", "orders"), ).table @logger.inject_lambda_context @router.get( "/orders", status_code=200, dependencies=[Depends(admin_only)], ) def get_orders(): logger.info(f"Getting orders") dynamo_response = orders_table.scan() if "Items" not in dynamo_response: raise HTTPException(status_code=404, detail="No orders found") orders = [Order(**o).clean() for o in dynamo_response.get("Items")] logger.info(f"Returning {len(orders)} orders") return fastapi_gateway_response(200, {}, orders)
test_get_orders.py
#test_get_orders.py import pytest import logging from unittest.mock import patch from freezegun import freeze_time from botocore.stub import Stubber from fastapi.testclient import TestClient from src.api import app from src.routes.get_orders import orders_table logger = logging.getLogger() test_client = TestClient(app, headers={"Authorization": "Bearer TOKEN"}) @pytest.fixture(autouse=True, scope="function") def orders_dynamodb_stub(): with Stubber(orders_table.meta.client) as ddb_stubber: yield ddb_stubber ddb_stubber.assert_no_pending_responses() @freeze_time("2024-03-22 12:00:00") @patch("src.routes.get_orders.admin_only", return_value=True) def test_handler_valid_event_get_orders(orders_dynamodb_stub): orders_dynamodb_stub.add_response( "scan", { "Items": [ { "order_id": {"S": "ORDER-1"}, "dessert_id": {"S": "DESSERT-1"}, "dessert_name": {"S": "Chocolate Cake"}, "quantity": {"N": "1"}, "customer_first_name": {"S": "jane"}, "customer_last_name": {"S": "doe"}, "customer_email": {"S": "jane.doe@gmail.com"}, "customer_phone_number": {"S": "555-555-5555"}, "customer_zip_code": {"S": "90210"}, "delivery_address": {"S": "123 Main St, Los Angeles, CA 90210"}, "scheduled_delivery_time": {"N": "1711108800"}, "order_total": {"N": "10.00"}, "order_status": {"S": "NEW"}, "order_date": {"N": "1711108800"}, }, ], }, expected_params={"TableName": "orders"}, ) response = test_client.get("/v1/orders") pytest.helpers.assert_responses_equal( response, 200, [ { "order_id": "ORDER-1", "dessert_id": "DESSERT-1", "dessert_name": "Chocolate Cake", "quantity": 1, "customer_first_name": "jane", "customer_last_name": "doe", "customer_email": "jane.doe@gmail.com", "customer_phone_number": "555-555-5555", "customer_zip_code": "90210", "delivery_address": "123 Main St, Los Angeles, CA 90210", "scheduled_delivery_time": 1711108800, "order_total": 10.00, "order_status": "NEW", "order_date": 1711108800, } ], )
原因分析
- 核心问题:路由加载时机早于补丁时机
FastAPI在导入app实例时会立即加载所有注册的路由,此时/orders路由的依赖项Depends(admin_only)已经绑定了原始的admin_only函数对象。而测试代码中from src.api import app是在模块顶部执行的,早于测试函数的补丁操作。后续的patch只能修改get_orders模块中admin_only变量的引用,无法改变已经绑定到路由依赖中的原始函数对象。 - 测试函数未接收
patch生成的mock对象,虽不是补丁失效的主因,但不符合mock使用规范。
修复方案
方案一:延迟app导入,确保补丁生效后加载路由
修改测试文件,将app的导入移到测试函数内部,让补丁先生效再加载路由:
# 修改后的test_get_orders.py关键部分 @freeze_time("2024-03-22 12:00:00") @patch("src.routes.get_orders.admin_only", return_value=True) def test_handler_valid_event_get_orders(mock_admin_only, orders_dynamodb_stub): # 补丁生效后再导入app并创建TestClient from src.api import app test_client = TestClient(app, headers={"Authorization": "Bearer TOKEN"}) # 后续测试逻辑不变 orders_dynamodb_stub.add_response(...) response = test_client.get("/v1/orders") ...
同时删除模块顶部的from src.api import app和test_client定义。
方案二:修改导入方式,让依赖引用原模块函数
修改get_orders.py的导入方式,避免直接复制函数对象,转而引用原模块的函数:
# get_orders.py修改导入部分 import src.lib.authorization as authorization # 路由依赖更新为 @router.get( "/orders", status_code=200, dependencies=[Depends(authorization.admin_only)], )
然后测试时直接补丁原模块的函数:
# 测试函数的装饰器修改为 @patch("src.lib.authorization.admin_only", return_value=True)
这样补丁会直接修改原模块的admin_only函数,路由依赖引用的是原模块的函数,补丁即可生效。
内容的提问来源于stack exchange,提问作者Tony96
相关产品推荐
相关产品推荐

