您如何使用模拟模拟只读属性?
我试过了:
setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
但问题在于它随后适用于该类的所有实例...这破坏了我的测试。
您还有其他想法吗?我不想模拟整个对象,而只是模拟这个特定的属性。
Answers:
我认为更好的方法是将该属性模拟为PropertyMock
,而不是__get__
直接模拟该方法。
它在文档中进行了说明,搜索unittest.mock.PropertyMock
:旨在用作类的属性或其他描述符的模拟。PropertyMock
提供__get__
和__set__
方法,以便您可以在获取返回值时指定它。
方法如下:
class MyClass:
@property
def last_transaction(self):
# an expensive and complicated DB query here
pass
def test(unittest.TestCase):
with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
实际上,答案是(与往常一样)在文档中,只是我在遵循示例时将补丁应用到实例而不是类。
这是操作方法:
class MyClass:
@property
def last_transaction(self):
# an expensive and complicated DB query here
pass
在测试套件中:
def test():
# Make sure you patch on MyClass, not on a MyClass instance, otherwise
# you'll get an AttributeError, because mock is using settattr and
# last_transaction is a readonly property so there's no setter.
with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
myclass = MyClass()
print myclass.last_transaction
mock.PropertyMock
是这样做的方法!
PropertyMock
时还不存在。
如果您要覆盖其属性的对象是模拟对象,则不必使用patch
。
而是可以创建一个PropertyMock
,然后在模拟类型上覆盖该属性。例如,要覆盖mock_rows.pages
属性以返回(mock_page, mock_page,)
:
mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
可能是样式问题,但是如果您在测试中更喜欢装饰器,@ jamescastlefield的答案可以更改为以下形式:
class MyClass:
@property
def last_transaction(self):
# an expensive and complicated DB query here
pass
class Test(unittest.TestCase):
@mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
def test(self, mock_last_transaction):
mock_last_transaction.return_value = Transaction()
myclass = MyClass()
print myclass.last_transaction
mock_last_transaction.assert_called_once_with()
如果您将pytest
与一起使用pytest-mock
,则可以简化代码,也可以避免使用上下文管理器,即with
如下语句:
def test_name(mocker): # mocker is a fixture included in pytest-mock
mocked_property = mocker.patch(
'MyClass.property_to_be_mocked',
new_callable=mocker.PropertyMock,
return_value='any desired value'
)
o = MyClass()
print(o.property_to_be_mocked) # this will print: any desired value
mocked_property.assert_called_once_with()
如果您需要嘲笑@property
以依赖原始版本__get__
,则可以创建自定义MockProperty
class PropertyMock(mock.Mock):
def __get__(self, obj, obj_type=None):
return self(obj, obj_type)
用法:
class A:
@property
def f(self):
return 123
original_get = A.f.__get__
def new_get(self, obj_type=None):
return f'mocked result: {original_get(self, obj_type)}'
with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
mock_foo.side_effect = new_get
print(A().f) # mocked result: 123
print(mock_foo.call_count) # 1
@property
。当其他答案(以及许多其他问题的其他答案)不起作用时,此答案对我有用。