Python中如何定义嵌套Mock对象?测试内部Mock失效问题解决
问题修复与优化方案
问题背景
我在编写单元测试时遇到了Mock不生效的问题:某个类的变量包含另一个类,我在conftest.py中定义了fixture并通过monkeypatch替换Mock对象,虽然能拿到预期的Mock实例,但新建对象的内部嵌套对象并非我定义的Mock,导致预期返回"Bye"的断言失败。
示例代码
module_a.py
class Connection: def get(self, name): return f"Hello {name}"
utils.py
from main.module_a import Connection class Util: def __int__(self): # 注意:此处存在笔误,Python构造函数应为`__init__` self.conn: Connection = Connection()
module_main.py
from main.utils import Util def main() -> str: util: Util = Util() msg: str = util.conn.get(name="Joe") return msg
conftest.py(原错误实现)
from unittest.mock import Mock import pytest from main.module_a import Connection from main.utils import Util @pytest.fixture(scope="function") def util_mock(monkeypatch): conn_mock: Mock = Mock(spec=Connection) conn_mock.get.return_value = "Bye" util_mock: Mock = Mock(spec=Util, conn=conn_mock) monkeypatch.setattr(target="main.module_main.Util", name=util_mock) return util_mock
test_module_main.py
from unittest.mock import Mock from main import module_main def test_main(util_mock: Mock): msg: str = module_main.main() test: str = util_mock.conn.get(name="foot") assert test == "Bye" # 直接调用fixture中的Mock正常 assert msg == "Bye" # 新建对象后断言失败
问题根源
- 构造函数笔误:
utils.py中Util类的构造函数写成了__int__,这不是Python的构造方法,导致self.conn不会被初始化(若修正为__init__,则进入第二个问题)。 - Mock策略错误:原代码直接将
Util类替换为一个Mock实例,但main()中调用Util()创建新对象时,Mock实例的__call__方法会返回全新的Mock对象,而非你预先设置好conn属性的util_mock,因此新对象的conn并非你定义的conn_mock。
修复方案
方案一:Mock底层Connection类(推荐)
直接MockConnection类,让所有新建的Connection实例都使用预设的Mock对象,这样无论Util如何创建实例,都会使用我们的Mock:
优化后的conftest.py
from unittest.mock import Mock import pytest @pytest.fixture(scope="function") def conn_mock(monkeypatch): # 创建Connection的Mock实例,设置get方法返回值 mock_conn_instance = Mock() mock_conn_instance.get.return_value = "Bye" # 让Connection类被调用时返回这个Mock实例 mock_conn_cls = Mock(return_value=mock_conn_instance) monkeypatch.setattr("main.utils.Connection", mock_conn_cls) return mock_conn_instance
测试用例简化
from main import module_main def test_main(conn_mock): msg = module_main.main() # 验证返回值符合预期 assert msg == "Bye" # 验证get方法被正确调用 conn_mock.get.assert_called_once_with(name="Joe")
方案二:MockUtil的构造方法
如果只想针对Util类做Mock,可以替换其__init__方法,让所有Util实例都使用预设的conn_mock:
优化后的conftest.py
from unittest.mock import Mock import pytest from main.utils import Util @pytest.fixture(scope="function") def conn_mock(monkeypatch): conn_mock = Mock() conn_mock.get.return_value = "Bye" # 替换Util的构造方法,直接设置conn属性 def mock_util_init(self): self.conn = conn_mock monkeypatch.setattr(Util, "__init__", mock_util_init) return conn_mock
总结
原方案的核心问题是错误地将Util类替换为单个Mock实例,而忽略了Util()会创建新对象的逻辑。推荐使用方案一,因为它直接Mock底层依赖,测试逻辑更清晰,也能覆盖更多场景。
内容的提问来源于stack exchange,提问作者JumbleGee
相关产品推荐
相关产品推荐

