Python-对象MagicMock不能在'await'表达式中使用

sha*_*hia 6 python unit-testing asynchronous mocking

当我尝试使用MagicMock模拟单元测试中的异步函数时,出现了以下异常:

TypeError:对象MagicMock不能在'await'表达式中使用

带有如下示例代码:

# source code
class Service:
    async def compute(self, x):
        return x

class App:
    def __init__(self):
        self.service = Service()

    async def handle(self, x):
        return await self.service.compute(x)

# test code
import asyncio
import unittest
from unittest.mock import patch


class TestApp(unittest.TestCase):
    @patch('__main__.Service')
    def test_handle(self, mock):
        loop = asyncio.get_event_loop()
        app = App()
        res = loop.run_until_complete(app.handle('foo'))
        app.service.compute.assert_called_with("foo")

if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

我应该如何使用内置的python3库修复它?

z0r*_*z0r 11

您可以获得模拟以返回可以使用Future等待的对象。以下是一个pytest测试用例,但应该可以使用unittest 进行类似的操作。

async def test_that_mock_can_be_awaited():
    mock = MagicMock(return_value=Future())
    mock.return_value.set_result(123)
    result = await mock()
    assert result == 123
Run Code Online (Sandbox Code Playgroud)

在您的情况下,由于您正在修补Service(作为 传入mock),因此mock.return_value = Future()应该可以解决问题。


sha*_*hia 8

我最终遇到了这种骇客。

# monkey patch MagicMock
async def async_magic():
    pass

MagicMock.__await__ = lambda x: async_magic().__await__()
Run Code Online (Sandbox Code Playgroud)

它仅适用于MagicMock,不适用于其他预定义的return_value


小智 8

shaun shia 提供了非常好的通用解决方案,但我发现在 python 3.8 中你可以只使用 @patch('__main__.Service', new=AsyncMock)


Thu*_*kwa 7

在python 3.8+中,您可以使用 AsyncMock

async def test_that_mock_can_be_awaited():
   mock = AsyncMock()
   mock.x.return_value = 123
   result = await mock.x()
   assert result == 123
Run Code Online (Sandbox Code Playgroud)

AsyncMock对象的行为使对象被识别为异步函数,并且调用的结果是可等待的。

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())
True
Run Code Online (Sandbox Code Playgroud)


Tom*_*iak 6

当尝试使用Python < 3.8 中的模拟对象时,我发现此注释非常有用。await您只需创建一个子类AsyncMock,该子类继承MagicMock并覆盖一个__call__方法即可成为协程:

class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super(AsyncMock, self).__call__(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

然后,在您的测试中执行以下操作:

@pytest.mark.asyncio
async def test_my_method():
    # Test "my_method" coroutine by injecting an async mock
    my_mock = AsyncMock()
    assert await my_method(my_mock)
Run Code Online (Sandbox Code Playgroud)

你可能还想安装pytest-asyncio