如何对含自定义verify_jwt装饰器的Flask路由做单元测试?
Flask路由单元测试:绕过自定义JWT装饰器的正确姿势
核心问题分析
你的自定义verify_jwt装饰器和路由函数在同一模块,导入时装饰器已经完成对函数的包裹,常规补丁方式失效的原因通常是:
- 补丁了
flask_jwt_extended.verify_jwt_in_request而非模块内导入的引用 - 导入模块后才补丁装饰器,此时函数已经被原装饰器处理过
可行解决方案
方案1:补丁装饰器内的JWT验证函数
直接替换模块内verify_jwt_in_request的实现,让它在测试中不抛出异常(或模拟异常)。
假设你的业务模块app.py代码如下:
from flask import Flask, jsonify from flask_jwt_extended import verify_jwt_in_request import logging app = Flask(__name__) def verify_jwt(f): def wrapper(*args, **kwargs): try: verify_jwt_in_request() return f(*args, **kwargs) except Exception as e: logging.error(f"JWT验证失败: {e}") return jsonify({"msg": "未授权"}), 401 return wrapper @app.route('/protected') @verify_jwt def protected_route(): return jsonify({"msg": "访问成功"}), 200
测试代码(用pytest):
from app import app import pytest def test_valid_jwt_access(monkeypatch): # 替换app模块内的verify_jwt_in_request,模拟验证通过 def mock_verify(): pass monkeypatch.setattr('app.verify_jwt_in_request', mock_verify) client = app.test_client() resp = client.get('/protected') assert resp.status_code == 200 assert resp.get_json() == {"msg": "访问成功"} def test_invalid_jwt_access(monkeypatch): # 模拟验证失败抛出异常 def mock_verify(): raise Exception("无效token") monkeypatch.setattr('app.verify_jwt_in_request', mock_verify) client = app.test_client() resp = client.get('/protected') assert resp.status_code == 401 assert resp.get_json() == {"msg": "未授权"}
方案2:绕过整个自定义装饰器
在导入业务模块前,将verify_jwt替换为无操作装饰器(直接返回原函数),彻底跳过JWT验证逻辑。
测试代码:
import sys from unittest.mock import Mock from flask import Flask import pytest def test_bypass_jwt_decorator(): # 提前mock app模块,替换verify_jwt为无操作装饰器 mock_verify_jwt = Mock(side_effect=lambda f: f) mock_app = Mock() mock_app.verify_jwt = mock_verify_jwt mock_app.app = Flask(__name__) # 将mock模块注入sys.modules,确保后续导入的是mock版本 sys.modules['app'] = mock_app # 现在导入路由函数,它不会被原装饰器包裹 from app import protected_route, app # 重新绑定路由(因为mock模块的路由未初始化) @app.route('/protected') def temp_route(): return protected_route() client = app.test_client() resp = client.get('/protected') assert resp.status_code == 200 assert resp.get_json() == {"msg": "访问成功"}
关键注意事项
- 补丁路径必须是业务模块内的函数引用(如
app.verify_jwt_in_request),而非原库的全局路径,因为装饰器调用的是模块内导入的本地引用。 - 若要替换装饰器,必须在
import app之前完成mock,否则函数已经被原装饰器处理,补丁无效。
内容的提问来源于stack exchange,提问作者mokhinurra
相关产品推荐
相关产品推荐

