Flask-SQLAlchemy 3.0查询Mock始终返回None的单元测试问题
Flask-SQLAlchemy 3.0 Mock查询返回None的解决办法
问题场景
在Flask应用中Mock Flask-SQLAlchemy 3.0的查询时,明明给Mock的查询链赋值了User实例,但代码里的user变量始终返回None。
测试用例代码:
class TestVerify(TestCase): @patch("middleware.operations.models.user.User") def test_verify_token_request(self, user_query_mock: MagicMock) -> None: user_query_mock.query.return_value.filter_by.return_value.first.return_value = ( User(user_id=4) ) def verified_func(*args, **kwargs) -> bool: del args, kwargs return True with app.test_request_context( data=json.dumps({"user_id": 123}), content_type="application/json" ): response = verify_token_request(verified_func) result = response() print(result)
被测试的装饰器函数:
def verify_token_request(func: Callable) -> Callable: @wraps(func) def wrapper(*args, **kwargs) -> str: user = User.query.filter_by(user_id=request.json["user_id"]).first() print(user) return func(*args, **kwargs) return wrapper
问题原因
Flask-SQLAlchemy 3.x里,User.query不是直接挂在User类上的属性,而是由项目里的SQLAlchemy实例(一般叫db)提供的查询描述符。你现在patch的是模型模块里的User类,但实际代码中调用的User.query会指向db.session.query(User)这条链,所以你的Mock设置根本没作用到实际执行的查询上。
解决办法
办法1:Patch正确的User.query路径
要Patch被测试函数(装饰器)所在模块中导入的User类的query属性,而不是模型原模块里的User。比如如果装饰器verify_token_request在middleware.operations.auth模块,就patch这个模块里的User.query:
# 替换成你装饰器所在的实际模块路径 @patch("middleware.operations.auth.User.query") def test_verify_token_request(self, user_query_mock: MagicMock) -> None: # 直接给query链的返回值赋值 user_query_mock.filter_by.return_value.first.return_value = User(user_id=4) def verified_func(*args, **kwargs) -> bool: del args, kwargs return True with app.test_request_context( data=json.dumps({"user_id": 123}), content_type="application/json" ): response = verify_token_request(verified_func) result = response() print(result)
办法2:Mockdb.session.query
既然User.query本质上等价于db.session.query(User),直接patchdb.session的query方法也能解决:
from middleware.operations.models import db @patch.object(db.session, 'query') def test_verify_token_request(self, query_mock: MagicMock) -> None: mock_user = User(user_id=4) query_mock.return_value.filter_by.return_value.first.return_value = mock_user # 后续测试代码和之前一致
办法3:用内存数据库替代Mock
如果不想折腾Mock,Flask-SQLAlchemy支持直接用内存SQLite数据库做测试,直接插入测试数据更直观:
def test_verify_token_request(self) -> None: # 配置内存数据库 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' db.init_app(app) with app.app_context(): db.create_all() # 添加测试用户 test_user = User(user_id=123) db.session.add(test_user) db.session.commit() def verified_func(*args, **kwargs) -> bool: del args, kwargs return True with app.test_request_context( data=json.dumps({"user_id": 123}), content_type="application/json" ): response = verify_token_request(verified_func) result = response() print(result)
内容的提问来源于stack exchange,提问作者HashNoxt
相关产品推荐
相关产品推荐

