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

Python中如何Mock被装饰类的方法?问题分析与解决

问题

在给带装饰器的Python类应用mock时遇到问题:不带装饰器时,patch能正常生效,但添加装饰器后,同样的patch操作就失效了。需要了解背后原因,以及测试被装饰类的最佳方法。

最小复现代码如下:

模块代码(module.py)

import functools


def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper


@decorator  # 注释掉这行,测试就能通过
class Something:
    def do_external(self):
        raise Exception("should be mocked")

    def run(self):
        self.do_external()

测试代码(test_module.py)

from unittest import TestCase
from unittest.mock import Mock, patch
from module import Something


class TestModule(TestCase):
    @patch('module.Something.do_external', Mock())
    def test_module(self):
        s = Something()
        s.run()
原因分析

当用装饰器装饰类时,@decorator本质是把Something类传入decorator函数,随后module.Something变量会指向装饰器返回的wrapper函数——哪怕用了functools.wraps,它也只是复制原类的元数据,并没有改变wrapper是函数而非类的本质。

而unittest.mock.patch('module.Something.do_external')是尝试给module.Something这个对象的do_external属性打补丁,但此时module.Something已经变成了wrapper函数,不是原来的类了。原类被包装在wrapper内部,patch根本找不到它的do_external方法,自然失效。

注释掉装饰器时,module.Something就是原类,patch能直接修改类的属性,所以可以正常工作。

解决方法

方法1:修改装饰器,让它返回类而非函数

如果装饰器是你自己实现的,调整逻辑让它返回一个继承原类的子类,而非函数。这样module.Something仍然是类,patch可以正常生效:

import functools

def decorator(cls):
    @functools.wraps(cls, updated=())  # updated=()避免覆盖类的特殊属性
    class Wrapper(cls):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    return Wrapper

方法2:通过__wrapped__拿到原类再patch

因为用了functools.wraps,可以通过Something.__wrapped__获取被装饰器包裹的原类,直接patch原类的方法:

class TestModule(TestCase):
    def test_module(self):
        original_cls = Something.__wrapped__
        with patch.object(original_cls, 'do_external', Mock()):
            s = Something()
            s.run()

方法3:直接patch实例的方法

创建实例后,直接替换实例的do_external方法,这种方式不受类是否被装饰的影响:

class TestModule(TestCase):
    def test_module(self):
        s = Something()
        s.do_external = Mock()
        s.run()

方法4:用patch.object直接patch原类

结合__wrapped__和patch.object,可以写成装饰器形式:

class TestModule(TestCase):
    @patch.object(Something.__wrapped__, 'do_external', Mock())
    def test_module(self):
        s = Something()
        s.run()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:20:33