单元测试中无法Mock函数装饰器?类型校验报错求解决方案
解决单元测试中类型校验装饰器拦截MagicMock的问题
问题场景
我在给Calculator类的call方法编写单元测试,该方法通过check_types_method装饰器做参数类型校验:
class Calculator: @check_types_method def call(self, a: MegaNumber, b: int) -> int: return a.value + b
测试时想用MagicMock替代MegaNumber对象,却触发类型校验报错:
TypeError: The argument 'a' must be of type <class 'mega_number.MegaNumber'>, received: MagicMock
之前尝试用@patch('calculator.check_types_method', lambda x: x)装饰测试类,但因为装饰器在模块加载时就已生效,patch时机太晚导致无效。
可行解决方案
方法1:直接替换被装饰后的类方法
因为装饰器在类加载时已应用到方法上,我们可以直接替换Calculator.call为原始未被装饰的方法(需确保装饰器使用了functools.wraps保留原始方法):
from calculator import Calculator from unittest.mock import MagicMock def test_calculator_call(): # 获取原始未被装饰的方法 original_call = Calculator.call.__wrapped__ # 替换类方法为原始方法 Calculator.call = original_call # 执行测试逻辑 mock_mega = MagicMock() mock_mega.value = 5 calc = Calculator() assert calc.call(mock_mega, 3) == 8 # 测试完成后恢复原方法(避免影响其他测试用例) Calculator.call = original_call
如果装饰器未使用functools.wraps,可以直接重新定义一个无校验的临时方法替换:
def test_calculator_call(): # 定义临时无校验方法 def temp_call(self, a, b): return a.value + b original_call = Calculator.call Calculator.call = temp_call # 测试逻辑... mock_mega = MagicMock(value=5) assert Calculator().call(mock_mega, 3) == 8 # 恢复原方法 Calculator.call = original_call
方法2:给装饰器添加测试跳过开关
修改check_types_method.py中的装饰器,增加全局开关控制是否跳过校验:
import functools # 测试模式开关,默认关闭 SKIP_TYPE_CHECK = False def check_types_method(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): # 测试模式下直接执行原方法 if SKIP_TYPE_CHECK: return func(self, *args, **kwargs) # 原有类型校验逻辑 # ...(此处保留你的类型校验代码) return func(self, *args, **kwargs) return wrapper
在测试代码中开启开关后执行测试,最后恢复状态:
from calculator import Calculator from unittest.mock import MagicMock from check_types_method import SKIP_TYPE_CHECK def test_calculator_call(): SKIP_TYPE_CHECK = True try: mock_mega = MagicMock(value=5) assert Calculator().call(mock_mega, 3) == 8 finally: # 确保开关恢复,不影响其他测试 SKIP_TYPE_CHECK = False
方法3:提前patch装饰器并重新加载模块
在calculator模块加载前就patch装饰器,再重新加载模块让新的装饰器生效:
from unittest.mock import patch, MagicMock import importlib def test_calculator_call(): # 先patch装饰器为无操作的lambda with patch('check_types_method.check_types_method', lambda x: x): # 重新加载calculator模块,应用patch后的装饰器 import calculator importlib.reload(calculator) # 执行测试 mock_mega = MagicMock(value=5) calc = calculator.Calculator() assert calc.call(mock_mega, 3) == 8
内容的提问来源于stack exchange,提问作者stas stas
相关产品推荐
相关产品推荐

