Python unittest中Mock其他模块导入的类属性的方法
问题描述
项目结构
├───pa │ |───__init__.py | |─── a1.py | |─── test_a1.py | ├───pb │ |───__init__.py | |─── b1.py
对应代码
b1.py
class B1: def __init__(self): self.text = "Unmocked_B1" def get_text(self): return self.text
pb/init.py
from .b1 import B1
a1.py
from src.pb import B1 class A1: def __init__(self): self.b = B1() def get_text(self): return self.b.get_text()
pa/init.py
from .a1 import A1
需求
使用unittest.mock.patch mock A1类中的self.b属性,让test_a1.py执行后打印出Mocked B1。当前测试代码框架如下:
from unittest import TestCase from unittest.mock import patch, PropertyMock from . import A1 class TestB1(TestCase): @patch('Some args') def test_get_text(self, mocked_b1): # Some code a1 = A1() print(a1.get_text()) # Should print: Mocked B1
无效尝试
- Mock B1原始类
@patch('src.pb.b1.B1') def test_get_text(self, mocked_b1): mocked_b1.get_text.return_value = 'Mocked B1' a1 = A1() print(a1.get_text()) # 未得到预期结果
- 用PropertyMock mock类属性
@patch('pa.a1.A1.b', new_callable=PropertyMock) def test_get_text(self, mocked_b): mocked_b.get_text.return_value = 'Mocked B1' a1 = A1() print(a1.get_text()) # 未得到预期结果
需要找到支持动态属性场景的有效mock方式,使用Python 3.10。
解决方案
方法一:Mock被测试模块中的B1引用
问题出在mock路径错误——必须mock被测试代码实际导入的B1引用(即pa.a1模块中的B1),而非原始类的路径。修改后的测试代码:
from unittest import TestCase from unittest.mock import patch from . import A1 class TestB1(TestCase): @patch('pa.a1.B1') def test_get_text(self, mocked_b1): # 给mock类的实例配置返回值 mocked_b_instance = mocked_b1.return_value mocked_b_instance.get_text.return_value = 'Mocked B1' a1 = A1() print(a1.get_text()) # 输出: Mocked B1
解释
@patch('pa.a1.B1')替换的是A1模块内导入的B1类,当A1初始化调用B1()时,会生成这个mock类的实例。- 必须给mock类的实例(
mocked_b1.return_value)设置get_text的返回值,而非直接给mock类设置,因为self.b是实例对象。
方法二:直接替换A1实例的b属性
如果不想mock整个B1类,可以直接替换已创建A1实例的self.b属性:
from unittest import TestCase from unittest.mock import Mock, patch from . import A1 class TestB1(TestCase): def test_get_text(self): a1 = A1() # 创建mock的B1实例 mocked_b = Mock() mocked_b.get_text.return_value = 'Mocked B1' # 临时替换a1的b属性 with patch.object(a1, 'b', mocked_b): print(a1.get_text()) # 输出: Mocked B1
也可以用装饰器跳过A1的初始化,手动设置mock属性:
from unittest import TestCase from unittest.mock import Mock, patch from . import A1 class TestB1(TestCase): @patch.object(A1, '__init__', return_value=None) def test_get_text(self, mock_init): a1 = A1() mocked_b = Mock() mocked_b.get_text.return_value = 'Mocked B1' a1.b = mocked_b print(a1.get_text()) # 输出: Mocked B1
无效原因说明
- 第一种尝试:mock的是原始模块的
src.pb.b1.B1,但A1模块已经导入了B1的引用,mock原始模块不会影响已导入的引用,必须mock被测试模块内的引用。 - 第二种尝试:
A1.b是实例属性(在__init__中赋值的self.b),而PropertyMock仅用于mock类的@property装饰属性,对实例属性无效。
内容的提问来源于stack exchange,提问作者piotre10
相关产品推荐
相关产品推荐

