【问题标题】:Python - object MagicMock can't be used in 'await' expressionPython - 对象 MagicMock 不能用于“等待”表达式
【发布时间】:2018-07-18 05:49:41
【问题描述】:

当我尝试使用 MagicMock 在 unittest 中模拟异步函数时,我遇到了这个异常:

TypeError: object MagicMock can't be used in 'await' expression

示例代码如下:

# 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()

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

【问题讨论】:

标签: python unit-testing asynchronous mocking


【解决方案1】:

我最终得到了这个 hack。

# monkey patch MagicMock
async def async_magic():
    pass

MagicMock.__await__ = lambda x: async_magic().__await__()

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

【讨论】:

  • 这个解决方法也对我有用!非常感谢!
【解决方案2】:

您可以通过使用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

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

【讨论】:

  • 该解决方案在 Python 3.7 中对我来说很好。但它会在 Python 3.8 中引发类似 AttributeError: '_asyncio.Future' object has no attribute '<sub-mocked-function>' 的错误。
  • 在 Linux 上的 Python 3.8.10 中为我工作。
【解决方案3】:

在 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

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

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())
True

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 谢谢,到处找这个!
    • 这应该是正确答案!使用mock.AsyncMock
    【解决方案5】:

    我发现this comment 在 Python await 模拟对象时非常有用。您只需创建一个子类AsyncMock,它继承自MagicMock,并将__call__ 方法覆盖为协程:

    class AsyncMock(MagicMock):
        async def __call__(self, *args, **kwargs):
            return super(AsyncMock, self).__call__(*args, **kwargs)
    

    然后,在你的测试中,做:

    @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)
    

    您可能还想安装pytest-asyncio

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-26
      • 2021-09-28
      • 1970-01-01
      • 2021-06-02
      • 2021-10-09
      相关资源
      最近更新 更多