当我尝试使用MagicMock模拟单元测试中的异步函数时,出现了以下异常:
TypeError:对象MagicMock不能在“等待”表达式中使用
带有示例代码,例如:
# 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库对其进行修复?
答案 0 :(得分:5)
shaun shia提供了非常好的通用解决方案,但是我发现在python 3.8中,您只能使用@patch('__main__.Service', new=AsyncMock)
答案 1 :(得分:4)
我最终遭到了这次黑客入侵。
# monkey patch MagicMock
async def async_magic():
pass
MagicMock.__await__ = lambda x: async_magic().__await__()
它仅适用于MagicMock,不适用于其他预定义的return_value
答案 2 :(得分:3)
当尝试在Python <3.8中使用await
模拟对象时,我发现this comment非常有用。您只需创建一个子类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
答案 3 :(得分: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()
应该可以解决问题。
答案 4 :(得分:1)
在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