如何在Python单元测试中Mock SQLAlchemy的Query Filter?
解决SQLAlchemy Query Filter的Mock问题,让循环执行并转换行到字典
核心问题
UnifiedAlchemyMagicMock不会自动生成带数据的查询结果,需要手动配置链式调用的返回值,同时要模拟模型实例的__table__.columns属性(因为row2dict依赖该属性遍历列名)。
具体实现步骤及代码示例
以下是基于pytest的完整测试代码,包含模型定义、目标函数和Mock配置:
from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base from alchemy_mock.mocking import UnifiedAlchemyMagicMock import pytest from unittest.mock import call # 定义测试用的someModel(实际项目中直接导入即可) Base = declarative_base() class someModel(Base): __tablename__ = 'some_table' id = Column(Integer, primary_key=True) fileName = Column(String) # 目标转换函数 def row2dict(row): d = {} for column in row.__table__.columns: d[column.name] = getattr(row, column.name) return d def test_query_filter_mock(): # 1. 创建模拟的模型行数据 mock_row = someModel() mock_row.id = 1 mock_row.fileName = "myfile" # 2. 模拟模型行的__table__.columns属性(row2dict依赖此遍历列名) mock_columns = [ type('MockColumn', (), {'name': 'id'})(), type('MockColumn', (), {'name': 'fileName'})() ] mock_row.__table__ = type('MockTable', (), {'columns': mock_columns})() # 3. 配置UnifiedAlchemyMagicMock的链式调用返回值 db = UnifiedAlchemyMagicMock() # 让filter返回包含模拟行的可迭代列表,确保for循环能执行 db.query.return_value.filter.return_value = [mock_row] # 4. 执行目标业务代码 data = db.query(someModel).filter(someModel.fileName == "myfile") result_list = [] for row in data: a = row2dict(row) result_list.append(a) # 验证结果正确性 assert result_list == [{'id': 1, 'fileName': 'myfile'}] # 验证Query和Filter的调用是否符合预期 db.query.assert_called_once_with(someModel) db.query.return_value.filter.assert_called_once_with( someModel.fileName == "myfile" )
关键说明
- 模拟可迭代结果:必须让
filter方法返回包含模拟行的列表(或其他可迭代对象),否则for循环会因无数据跳过。 - 模拟__table__.columns:
row2dict通过遍历该属性获取列名,若不模拟会抛出AttributeError,这里用动态类模拟列对象的name属性即可满足需求。 - 条件匹配验证:通过
assert_called_once_with可以验证Filter是否使用了预期的查询条件,确保Mock的准确性。
内容的提问来源于stack exchange,提问作者Devil
相关产品推荐
相关产品推荐

