在 Python 中模拟异步属性

问题描述 投票:0回答:2

下面的示例类有一个可等待的属性

bar
,如
async_main()
所示,因为它(理论上)在返回所有内容的答案之前会执行一些 IO 工作。

class Foo:

    @property
    async def bar(self):
        return 42


async def async_main():
    f = Foo()
    print(await f.bar)

我在测试它时遇到了麻烦,因为通常怀疑的 Mock、MagicMock 和 AsyncMock 不能按预期使用属性。我当前的解决方法是:

f.bar = some_awaitable()

因为这使得 f.bar 成为一个可以等待的“字段”,但不幸的是我需要在测试时多次访问它,这当然会在第二次访问时产生

RuntimeError: cannot reuse already awaited coroutine

是否有一种既定的方法来模拟这样的异步属性?

python properties python-asyncio
2个回答
2
投票

我能想到的最简单的方法是使用

bar
属性再次修补
async
以便进行测试。

我假设您想要测试

Foo
上的其他方法,并且该方法调用其
bar


code.py

from asyncio import run


class Foo:
    @property
    async def bar(self) -> int:
        return 42

    async def func(self) -> int:
        return await self.bar


async def main():
    f = Foo()
    print(await f.func())


if __name__ == '__main__':
    run(main())

test.py

from unittest import IsolatedAsyncioTestCase
from unittest.mock import patch

from . import code


class FooTestCase(IsolatedAsyncioTestCase):
    async def test_func(self) -> None:
        expected_output = 69420

        @property
        async def mock_bar(_foo_self: code.Foo) -> int:
            return expected_output

        with patch.object(code.Foo, "bar", new=mock_bar):
            f = code.Foo()
            # Just to see that our mocking worked:
            self.assertEqual(expected_output, await f.bar)
            # Should call `bar` property again:
            output = await f.func()
        self.assertEqual(expected_output, output)

参考资料:

patch
文档


0
投票

属性

Foo.bar
没有 setter,因此类
Foo
的实例无法将模拟对象分配给该属性。适当的解决方案是用
Foo.bar
修补
PropertyMock

import asyncio
from unittest import mock


async def test_bar():
    expected_value = 'expected value'

    async_class_mock = mock.AsyncMock(**{'coroutine.return_value': expected_value})
    task = asyncio.create_task(async_class_mock.coroutine())
    patch_property_with = mock.PropertyMock(spec=Foo, **{'bar': task})

    with mock.patch.object(target=Foo, attribute='bar', new=patch_property_with) as bar_mock:
        Foo.bar = bar_mock.bar
        assert await Foo().bar == expected_value

这是发生的事情:

  1. async_class_mock
    AsyncMock
    的实例,已使用伪协程初始化,其返回值设置为测试期望
    Foo.bar
    返回的值。
>>> async_class_mock.coroutine()
<coroutine object AsyncMockMixin._execute_mock_call at 0x00>
>>> await async_class_mock.coroutine()
'expected value'
  1. asyncio.create_task
    将安排在等待任务时执行
    async_class_mock.coroutine()
    。因为它是一个任务,所以你可以等待多次。
>>> task
<Task pending name='Task-1' coro=<AsyncMockMixin._execute_mock_call() running at ...>>
>>> await task
'expected value'
>>> await task
'expected value'
  1. patch_property_with
    PropertyMock
    的一个实例,它被分配给
    Foo.bar
    。它正在重新定义
    Foo.bar
    的含义。
>>> bar_mock
<PropertyMock spec='Foo' ...>
>>> bar_mock.bar
<Task pending name='Task-1' coro=<AsyncMockMixin._execute_mock_call() running at ...>>

>>> Foo.bar = bar_mock.bar

现在

Foo.bar
引用异步任务,可以等待多次以获得预期值。

>>> foo = Foo()
>>> await foo.bar
'expected value'
>>> await foo.bar == expected_value
True

上下文管理器退出后,应用于

Foo.bar
的补丁将被删除。

© www.soinside.com 2019 - 2024. All rights reserved.