关键在于 patch 业务模块中实际调用数据库连接的路径,如 "app.db.sqlite3.connect",而非全局 sqlite3.connect;模拟失败须用 side_effect 抛异常,推荐 fixture 或上下文管理器控制作用域。

pytest 中 mock 数据库连接失败,关键在 patch 哪个对象
不是 patch sqlite3.connect 或 psycopg2.connect 本身,而是 patch 你代码里实际调用它的那个模块路径。比如你的业务模块叫 app.db,里面写了 conn = sqlite3.connect("db.sqlite"),那就要 patch "app.db.sqlite3.connect" —— 路径必须和运行时 import 链完全一致,否则 mock 不生效。
常见错误是 patch 了 sqlite3.connect 却没效果,因为 Python 导入机制下,app.db 模块内部引用的是它自己 import 进来的 sqlite3 对象,不是全局的 sqlite3 模块顶层符号。
- 确认被测函数所在模块的 import 写法(例如
import sqlite3还是from sqlite3 import connect) - patch 目标格式统一为
"模块路径.函数名",如"app.db.sqlite3.connect"或"app.db.connect" - 使用
@patch装饰器时,参数顺序要和装饰器顺序反着来(后 patch 的先传入)
让 mock 抛出异常:用 side_effect 而不是 return_value
return_value 只能控制返回什么,模拟“成功”,而连接失败必须用 side_effect 才能触发异常流程。把它设为 sqlite3.Error、psycopg2.OperationalError 或任意你期望被捕获的异常类即可。
注意异常类型要和真实数据库驱动抛出的一致,否则上层 except 块可能捕获不到。比如 SQLAlchemy 通常包装底层异常,但直接调用驱动时就该用原生异常。
立即学习“Python免费学习笔记(深入)”;
- 正确写法:
mock_connect.side_effect = sqlite3.Error("unable to open database file") - 错误写法:
mock_connect.return_value = Exception(...)—— 这不会抛异常,只是返回一个 Exception 实例 - 若需多次调用不同行为(如第一次失败、第二次成功),
side_effect可设为异常列表:[sqlite3.Error(), MagicMock()]
避免 patch 后影响其他测试:优先用 fixture 而非全局 patch
在 conftest.py 或测试文件顶部用 @patch 装饰整个测试类,容易导致 mock 泄漏到不该被影响的测试中,尤其当多个测试共用同一模块路径时。更稳妥的做法是定义一个 pytest fixture,在需要的测试函数里显式请求。
fixture 内部用 monkeypatch.setattr 更轻量,且作用域天然限于当前测试函数;或者用 patch 上下文管理器,在 with 块内完成 patch,退出即自动恢复。
- 推荐 fixture 示例:
@pytest.fixture def mock_db_connect_failure(monkeypatch): monkeypatch.setattr("app.db.sqlite3.connect", Mock(side_effect=sqlite3.Error)) - 测试函数中直接写:
def test_fetch_user_fails_when_db_down(mock_db_connect_failure): - 不用 fixture 时,务必用
with patch(...) as mock_conn:包裹具体逻辑,别让 patch 跨出缩进块
验证是否真走到了异常分支:检查日志或断言副作用
只 mock 异常还不够,得确认被测函数确实执行到了 catch 分支,并按预期返回/记录/重试。最直接的方式是检查它是否调用了日志记录函数,或返回了 fallback 值(如空列表、None、默认对象)。
如果函数里有 logger.error,那就同样 mock 它并断言 mock_logger.error.assert_called_once();如果返回 None,就 assert 返回值为 None;如果触发了重试逻辑,可结合 side_effect 列表 + 断言调用次数。
- 别只测“没报错”,要测“做了该做的事”——比如连接失败时是否返回了友好的错误响应
- 如果被测函数里有
sys.exit(1)或os._exit,mock 它们防止测试进程意外终止 - 真实数据库连接失败往往伴随网络超时,可用
socket.timeout替代sqlite3.Error更贴近生产场景
最难调试的永远是 patch 路径写错,建议先在被测模块里加一行 print(sqlite3.connect),运行看输出的 <function connect at ...> 的 module 名,再照着写 patch 目标。


















