Python单元测试Mock失效:session_factory Mock未生效问题排查
问题原因与解决方法
问题根源
你的db.py模块在被导入时会立即执行所有顶层代码:包括实例化DatabaseSettings()、创建engine和session_factory。而测试中service.classA必然会导入db.py,这会在@patch生效之前就触发配置加载流程,导致mock根本来不及阻止配置初始化,所以依然会抛出环境变量缺失的异常。
解决方法
方案1:修改db.py,延迟初始化逻辑
将数据库初始化逻辑改为懒加载模式,避免顶层代码自动执行:
# db.py class DatabaseSettings(BaseSettings): username: str password: str name: str host: str sql_echo: bool = False class Config(abc.Config): env_prefix = f"{abc.Config.env_prefix}DB_" def url(self): return f"mysql+aiomysql://{self.username}:{self.password}@{self.host}/{self.name}" # 用函数封装初始化逻辑,需要时再调用 def get_session_factory(): config = DatabaseSettings() engine = create_async_engine(config.url(), echo=config.sql_echo) return sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
之后在service.py中,改为通过get_session_factory()获取会话工厂,测试时直接mock这个函数即可。
方案2:在导入service前mock配置类
如果不想修改db.py结构,必须确保mock在service模块导入前生效:
@patch("config.db.DatabaseSettings") @patch("config.db.session_factory") @pytest.mark.asyncio async def test_result(mock_session_factory, mock_db_settings): # 配置mock的DatabaseSettings返回假的数据库URL mock_config = mock_db_settings.return_value mock_config.url.return_value = "mysql+aiomysql://test:test@localhost/test_db" # 必须在mock完成后再导入service模块 from service.classA import A1 await A1()
方案3:用pytest fixture全局提前mock
将mock逻辑放到会话级fixture中,确保所有测试模块导入前完成mock:
import pytest from unittest.mock import patch @pytest.fixture(autouse=True, scope="session") def mock_db_config(): with patch("config.db.DatabaseSettings") as mock_db: mock_config = mock_db.return_value mock_config.url.return_value = "mysql+aiomysql://test:test@localhost/test_db" yield @pytest.mark.asyncio @patch("config.db.session_factory") async def test_result(mock_session_factory): from service.classA import A1 await A1()
关键提示
Python模块的顶层代码会在导入阶段立即执行,所有mock操作必须在这段代码执行前完成才能生效。
内容的提问来源于stack exchange,提问作者Moshiur Rahman
相关产品推荐
相关产品推荐

