如何用mock模拟readonly属性?

cha*_*lax 75 python unit-testing mocking

你如何用mock模拟readonly属性?

我试过了:

setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())
Run Code Online (Sandbox Code Playgroud)

但问题是它然后适用于所有类的实例...这打破了我的测试.

你还有其他想法吗?我不想模拟完整的对象,只有这个特定的属性.

小智 136

我认为更好的方法是将属性模拟为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()
Run Code Online (Sandbox Code Playgroud)

  • 我发现在上下文管理器调用中包含返回值稍微更清晰:```with mock.patch('MyClass.last_transaction',new_callable = PropertyMock,return_value = Transaction()):...``` (4认同)
  • 使用mock.patch.object也很好,因为您不必将类名写为字符串(在示例中并不是真正的问题),并且如果您决定重命名包但还没有这样做,则更容易检测/修复更新了一个测试 (3认同)
  • 这是它应该做的方式.我希望有一种方法可以移动"已接受"的答案 (2认同)

cha*_*lax 39

实际上,答案是(像往常一样)在文档中,只是当我按照他们的例子时,我正在将补丁应用于实例而不是类.

这是怎么做的:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass
Run Code Online (Sandbox Code Playgroud)

在测试套件中:

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
Run Code Online (Sandbox Code Playgroud)

  • 人们应该使用另一个例子.`mock.PropertyMock`就是这样做的! (12认同)
  • 没错,在撰写本文时,“PropertyMock”还不存在。 (5认同)

Tim*_*ast 7

如果要覆盖其属性的对象是模拟对象,则不必使用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
Run Code Online (Sandbox Code Playgroud)

  • 巴姆,正是我想要的(具有属性的自动指定对象)。还有一位同事的回复 ‍♂️ (2认同)

lmi*_*asf 6

如果您使用pytestwith 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()
Run Code Online (Sandbox Code Playgroud)


Eya*_*vin 5

可能是样式问题,但是如果您在测试中更喜欢装饰器,@ 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()
Run Code Online (Sandbox Code Playgroud)