如何在包装它时模拟python属性setter(即调用原始setter)?最直接的方法是访问__set__
,但它对于属性是只读的,因此无法正常工作。
from unittest import TestCase
from unittest.mock import patch, PropertyMock
class SomeClass:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value + 1
@value.setter
def value(self, value):
self._value = value + 1
class TestSomeClass(TestCase):
def test_value_setter(self):
instance = SomeClass(0)
with patch.object(SomeClass.value, '__set__', wraps=SomeClass.value.__set__) as value_setter:
instance.value = 1
value_setter.assert_called_with(instance, 1)
self.assertEquals(instance.value, 3)
还有文档中的new_callable=PropertyMock
,我已尝试将其与wrap
结合使用,但还没有让它发挥作用。
答案 0 :(得分:4)
您需要替换整个property
对象;你不能简单地更换二传手。这意味着您需要提供一个新的property
对象,其中setter是原始setter的模拟包装器(由property.fset
属性访问):
setter_mock = Mock(wraps=SomeClass.value.fset)
mock_property = SomeClass.value.setter(setter_mock)
with patch.object(SomeClass, 'value', mock_property):
instance.value = 1
setter_mock.assert_called_with(instance, 1)
我在这里重用了@property.setter
装饰器;它返回一个新的property
对象,其中包含原始property
的所有属性,除非将setter替换为传入的内容;见How does the @property decorator work?
答案 1 :(得分:1)
这是另一个重用PropertyMock
的解决方案,但缺点是只允许在上下文中模拟setter。
with patch.object(SomeClass, 'value', new_callable=PropertyMock, wraps=partial(
SomeClass.value.__set__, instance)
) as value:
instance.value = 1
value.assert_called_with(1)
self.assertEquals(instance.value, 3)
或
instance = SomeClass(0)
with patch.object(SomeClass, 'value', PropertyMock(
side_effect=partial(SomeClass.value.__set__, instance)
)) as value:
instance.value = 1
value.assert_called_with(1)
self.assertEquals(instance.value, 3)