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

如何用pytest-mock实现模块间的链式Mock?

链式调用场景下pytest-mock的正确实现方法

问题背景

在编写单元测试时使用mock技术,遇到模块链式调用场景下mock失效的问题。示例代码中,A.py定义的write_to_file被B.py的函数调用,B的函数又被C.py的feature函数调用,测试时mockwrite_to_file失败。更通用的场景是多层嵌套调用(A→B→C→...→N),需要找到正确的mock方式。

示例代码如下:

# file A.py
def write_to_file(message, file="test.txt"):
    with open(file, "w") as fh:
        fh.write(message)


# File B.py
from A import write_to_file

def part_of_a_big_feature():
    # Do some things
    write_to_file("bla")
    # Do some things


# File C.py(原示例中误写为feature.py)
from B import part_of_a_big_feature

def feature():
    # First part is for a specific feature

    # Second: I called the part_of_a_big_feature
    part_of_a_big_feature()

    # Then another feature

    # Then return
    return 1

# file test_my_function.py
import pytest

from C import feature

class TestException(Exception):
    pass

class TestFeature:
    @staticmethod
    def setup_method():
        pass

    @staticmethod
    def teardown_method():
        pass

    @staticmethod
    def test_feature(mocker):
        # Mocking路径错误,导致失效
        mocker.patch("path.to.A.Bwrite_to_file", return_value=None)
        
        # Tests
        assert feature() == 1

运行命令:pytest -vv test_my_function.py

核心原理

Python的import机制会将目标对象复制到当前模块的命名空间中。比如B.py中from A import write_to_file,相当于在B模块的命名空间里创建了一个指向A.write_to_file的引用,后续B中的函数调用的是这个本地引用,而非A模块里的原函数。因此,mock的关键是针对对象被实际调用的位置(即所在模块的本地引用)进行mock,而非对象的定义位置。

针对示例的修正

示例中,part_of_a_big_feature调用的是B模块本地的write_to_file引用,所以正确的mock路径应该是B.write_to_file(或完整项目路径,比如my_project.B.write_to_file)。

修正后的测试代码:

@staticmethod
def test_feature(mocker):
    # 正确mock路径:B模块中的write_to_file引用
    mock_write = mocker.patch("B.write_to_file", return_value=None)
    
    assert feature() == 1
    # 可选:验证mock函数确实被调用
    mock_write.assert_called_once_with("bla")

通用多层嵌套场景解决方案

不管链式调用嵌套多少层,只需遵循以下步骤:

  1. 定位调用链中直接调用目标函数的那个函数所在的模块
  2. 根据该模块的导入方式确定mock路径:
    • 如果是from 原模块 import 目标函数导入,mock路径为当前模块名.目标函数
    • 如果是import 原模块后用原模块.目标函数调用,mock路径为当前模块名.原模块.目标函数
  3. 使用mocker.patch()对上述路径进行mock

举个通用例子:

  • A.py定义test()
  • B.py通过from A import test导入,在function_that_use_test()中调用test()
  • C.py通过from B import function_that_use_test导入,在function_that_use_function_that_use_test()中调用该函数

此时,test()实际被调用的位置是B模块的命名空间,所以mock路径应为B.test(或完整模块路径)。

内容的提问来源于stack exchange,提问作者PicxyB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 21:15:44