
在 pytest 中使用 pytest-mock 模拟模块级变量时,应使用 new= 参数而非 return_value=,否则会意外返回 MagicMock 实例,导致如 Path.touch() 等真实方法失效。
在 pytest 中使用 pytest-mock 模拟模块级变量时,应使用 `new=` 参数而非 `return_value=`,否则会意外返回 `magicmock` 实例,导致如 `path.touch()` 等真实方法失效。
当你试图通过 mocker.patch(..., return_value=...) 去模拟一个模块变量(例如 DEFAULT = Path("/etc/config.yaml")),实际发生的是:patch 将该变量替换为一个 MagicMock,并将其 return_value 设为你指定的对象。这意味着 DEFAULT 本身变成了一个 mock,每次访问它时,其实是调用该 mock —— 它“返回”你的 Path 对象,但你拿到的始终是 MagicMock 实例,而非真正的 Path。
这就是为什么 DEFAULT.touch(exist_ok=True) 不创建文件:你调用的是 MagicMock.touch()(默认返回另一个 MagicMock),而非 pathlib.Path.touch()。MagicMock 默认允许任意属性/方法访问且不报错,因此 DEFAULT.resolve()、DEFAULT.exists() 看似“成功”,实则未执行任何真实逻辑。
✅ 正确做法:使用 new= 参数直接替换整个变量值:
@pytest.fixture(autouse=True)
def mock_default(mocker, tmp_path):
"""Mock DEFAULT as a real Path object."""
mock_path = tmp_path / "config.yaml"
mocker.patch("mypackage.mymodule.DEFAULT", new=mock_path)
此时 mypackage.mymodule.DEFAULT 被直接赋值为真实的 pathlib.Path 实例,所有路径操作(如 .touch()、.write_text()、.exists())均按预期执行。
⚠️ 注意事项:
- new= 是 unittest.mock.patch 的原生参数,pytest-mock 的 mocker.patch 完全支持;
- 不要混淆 return_value(用于 mock 函数/方法 的返回值)与 new(用于替换 变量/对象本身);
- 若需动态生成值(如每次测试返回不同路径),可用 new_callable + 自定义工厂函数,但多数场景 new= 已足够;
- 验证是否生效:在测试中打印 type(mymodule.DEFAULT),应输出
(或 WindowsPath),而非 。
总结:模拟常量、配置路径、全局对象等非可调用变量时,始终优先选用 new=;仅当 mock 函数行为(如 requests.get())时,才使用 return_value= 或 side_effect=。清晰区分“替换值”与“控制返回”,是写出可靠单元测试的关键。











