包装它时的python mock属性setter

时间:2015-01-09 15:49:08

标签: python properties mocking unit-testing

如何在包装它时模拟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结合使用,但还没有让它发挥作用。

2 个答案:

答案 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)