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

Python单元测试如何正确mock Razz类__init__避免环境变量报错

问题根因

mock不生效的核心原因是patch执行时机晚于真实Razz类的实例化逻辑:

  • event_bank_details.py在模块被导入的阶段,就会直接执行顶层代码PAYMENT_GATEWAY = Razz(),这一步会立刻调用真实Razz类的__init__方法读取环境变量
  • 你当前的测试逻辑要么是在模块导入完成、真实实例已经创建后才patch Razz类,要么patch目标错误,已经生成的全局PAYMENT_GATEWAY实例不会被后续的类patch替换,运行时依然会走真实逻辑,触发环境变量不存在的报错。

另外你的测试代码还有两个明显问题:

  • 仅使用了1个@patch装饰器,但测试函数入参写了mock_email、mock_object两个mock参数,参数和装饰器数量不匹配,运行本身就会报错
  • 注释里的mock方法写法存在笔误,create_account方法的正确mock路径应该是mock_obj.return_value.create_account.return_value,而非create_account.create_account
修复方案

方案1:直接patch全局实例(改造成本最低,推荐)

不需要patch Razz类本身,直接替换event_bank_details模块下已经存在的全局PAYMENT_GATEWAY对象即可,注意mock定义要在导入被测函数之前完成:

from unittest.mock import patch
import pytest

@pytest.mark.parametrize("input, expected_op", UPDATE_EVENT_BANK_DETAILS_INPUT)
@patch("event_bank_details.event_bank_details.PAYMENT_GATEWAY")
def test_update_event_bank_details(mock_payment_gateway, input, expected_op):
    # 提前定义mock方法的返回值
    mock_payment_gateway.create_contact.return_value = "test_contact_id"
    mock_payment_gateway.create_account.return_value = "test_fund_id"

    # 导入被测函数
    from event_bank_details.event_bank_details import update_bank_detials
    response = update_bank_detials(input)
    response.prepare()
    response_dict = response.get()
    
    assert response_dict["statusCode"] == expected_op
    # 可选:校验网关方法的调用逻辑是否符合预期
    mock_payment_gateway.create_contact.assert_called_once()
    mock_payment_gateway.create_account.assert_called_once_with("test_contact_id", input)

方案2:优化代码设计,避免顶层即时实例化

从代码结构层面调整,把顶层立即实例化改成懒加载,从根源上避免导入时触发真实逻辑:
首先修改event_bank_details.py的代码:

from payment_gateway.payment import Razz, PaymentGatewayErrors

PAYMENT_GATEWAY = None

def get_payment_gateway():
    global PAYMENT_GATEWAY
    if PAYMENT_GATEWAY is None:
        PAYMENT_GATEWAY = Razz()
    return PAYMENT_GATEWAY

def update_bank_detials(request: Request) -> Response:
    gateway = get_payment_gateway()
    contact_id = gateway.create_contact(body, event_id)         
    fund_id = gateway.create_account(contact_id, body)
    return resposes

调整后模块导入阶段不会触发Razz实例化,只有实际调用业务函数时才会创建实例,测试时直接patchget_payment_gateway方法即可,完全隔离真实Razz类的逻辑。

临时方案:注入测试用环境变量

如果暂时不想调整mock逻辑,也可以通过patch.dict注入假的环境变量绕过__init__的校验,但注意这个方案下你还需要mock后续的网络请求逻辑,避免发起真实接口调用:

@pytest.mark.parametrize("input, expected_op", UPDATE_EVENT_BANK_DETAILS_INPUT)
@patch.dict("os.environ", {
    "API_KEY": "test_key",
    "SECRET": "test_secret",
    "ACCOUNT_NUMBER": "test_acc"
})
def test_update_event_bank_details(input, expected_op):
    # 注意:这里还需要额外mock request.post方法,不然会发真实网络请求
    from event_bank_details.event_bank_details import update_bank_detials
    # 后续测试逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 07:06:19