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

Python多with open语句Mock失败求助:两次Patch返回相同数据

解决Python中Mock多次open调用返回不同内容的问题

你用两个@patch('builtins.open')装饰器的方式行不通,因为多次patch同一个对象时,后面的装饰器会覆盖前面的。Python装饰器的执行顺序是从下到上(最靠近测试函数的先生效),所以最终只有最上方的patch生效,导致两次open调用都返回{"hello":"yes"}。


正确做法:用单个patch + side_effect控制多次调用的返回值

我们可以给mock_open返回的文件对象的read()方法设置side_effect,让它按调用顺序返回不同的JSON字符串,这样第一次open(second()里的调用)返回第一个内容,第二次(first()里的调用)返回第二个内容。

示例代码(修改原函数以便测试):

from unittest.mock import patch, mock_open
import json

# 假设path已定义
path = "test_file.json"

def second():
    with open(path, 'r') as second_something:
        second_json = json.load(second_something)
    return second_json  # 添加返回值,方便测试断言

def first():
    second()
    with open(path, 'r') as first_something:
        first_json = json.load(first_something)
    return first_json  # 添加返回值,方便测试断言

class TestFirst:
    @patch('builtins.open', new_callable=mock_open)
    def test_first(self, mock_file):
        # 设置read()方法的顺序返回值
        mock_file.return_value.read.side_effect = ['{"hello":"yes"}', '{"world":"no"}']
        
        # 调用待测试函数
        result = first()
        
        # 断言结果符合预期
        assert result == {"world": "no"}
        
        # 验证open被正确调用两次
        mock_file.assert_has_calls([
            patch.call(path, 'r'),
            patch.call(path, 'r')
        ])

不修改原函数的测试方案:

如果不想修改原函数的返回值,可以同时mockjson.load,通过它的调用记录来验证两次open读取的内容:

from unittest.mock import patch, mock_open
import json

path = "test_file.json"

def second():
    with open(path, 'r') as second_something:
        second_json = json.load(second_something)

def first():
    second()
    with open(path, 'r') as first_something:
        first_json = json.load(first_something)

class TestFirst:
    @patch('json.load')
    @patch('builtins.open', new_callable=mock_open)
    def test_first(self, mock_file, mock_json_load):
        # 设置read()的顺序返回值
        mock_file.return_value.read.side_effect = ['{"hello":"yes"}', '{"world":"no"}']
        
        first()
        
        # 验证json.load被调用两次,且内容符合预期
        assert mock_json_load.call_count == 2
        
        # 检查第一次调用的内容
        first_call_content = mock_json_load.call_args_list[0][0][0].read()
        assert json.loads(first_call_content) == {"hello": "yes"}
        
        # 检查第二次调用的内容
        second_call_content = mock_json_load.call_args_list[1][0][0].read()
        assert json.loads(second_call_content) == {"world": "no"}

关键说明:

  • side_effect可以接收一个列表,每次调用对应的方法时,会按顺序返回列表中的下一个值。
  • 不要重复patch同一个内置对象(比如builtins.open),否则后一次patch会完全覆盖前一次的配置。

内容的提问来源于stack exchange,提问作者Bayare Deepak Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 04:45:46