如何在Pytest中全局Mock已导入的SqlAlchemy Session变量?
全局Mock SqlAlchemy scoped_session的解决方案
为什么原有Mock代码不生效?
业务模块(如app/user/users.py)在导入阶段就会将utils.sessions.session的引用保存到自身的命名空间中。直接patchutils.sessions.session只会修改原模块的变量,无法更新已经导入到业务模块的旧引用,导致Mock不生效。此外,你的mocked_session函数返回的scoped_session("xxx")写法错误——scoped_session需要传入sessionmaker实例,而非字符串。
解决方案一:修改代码结构(推荐)
通过工厂函数提供session,而非直接导出变量,这样只需Mock工厂函数就能全局生效,无需修改业务模块的调用逻辑(可兼容旧代码)。
1. 重构utils/sessions.py
# utils/sessions.py from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy import create_engine # 生产环境配置 def get_session(): return scoped_session(sessionmaker( bind=create_engine('your_prod_db_url'), autocommit=False, autoflush=False )) # 保持原有导出,兼容已有的业务代码 session = get_session()
2. 业务模块改用工厂函数(可选,也可继续用原导入,但推荐切换)
# app/user/users.py from utils.sessions import get_session class UserRepo: def get_user(self, id): # 调用工厂函数获取session return get_session().query(User).filter(User.id == id)
3. 全局Mock Fixture
# tests/conftest.py from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy import create_engine import pytest from unittest.mock import patch def mocked_session(): # 返回测试用的scoped_session实例(比如内存SQLite) return scoped_session(sessionmaker( bind=create_engine('sqlite:///:memory:'), autocommit=False, autoflush=False )) @pytest.fixture(autouse=True) def mock_session_factory(): # Mock工厂函数,全局替换所有session来源 with patch('utils.sessions.get_session', return_value=mocked_session()) as mock_get: yield mock_get
解决方案二:不修改业务代码,全局更新引用
如果不想改动现有代码,可以在Mock后重新加载所有使用session的业务模块,让模块重新导入新的session引用:
# tests/conftest.py import importlib from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy import create_engine import pytest from unittest.mock import patch # 导入所有依赖session的业务模块 import app.user.users import app.articles.articles def mocked_session(): return scoped_session(sessionmaker( bind=create_engine('sqlite:///:memory:'), autocommit=False, autoflush=False )) @pytest.fixture(autouse=True) def mock_all_sessions(): with patch('utils.sessions.session', new=mocked_session()) as mock_session: # 重新加载模块,使其引用新的session importlib.reload(app.user.users) importlib.reload(app.articles.articles) yield mock_session
这种方法需要维护所有依赖session的模块列表,模块较多时会比较繁琐,仅作为临时过渡方案。
内容的提问来源于stack exchange,提问作者bmjeon5957
相关产品推荐
相关产品推荐

