You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"  # 新建对象后断言失败

问题根源

  1. 构造函数笔误:utils.py中Util类的构造函数写成了__int__,这不是Python的构造方法,导致self.conn不会被初始化(若修正为__init__,则进入第二个问题)。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:10:40