在python 3.5中模拟异步调用

在python 3.5中模拟异步调用,python,python-asyncio,python-mock,Python,Python Asyncio,Python Mock,如何使用unittest.mock.patch模拟从一个本机协程到另一个本机协程的异步调用 我目前有一个相当尴尬的解决方案: class CoroutineMock(MagicMock): def __await__(self, *args, **kwargs): future = Future() future.set_result(self) result = yield from future return resul

如何使用
unittest.mock.patch
模拟从一个本机协程到另一个本机协程的异步调用

我目前有一个相当尴尬的解决方案:

class CoroutineMock(MagicMock):
    def __await__(self, *args, **kwargs):
        future = Future()
        future.set_result(self)
        result = yield from future
        return result
@patch('some.path')
def test(self, mock):
    f = asyncio.Future()
    f.set_result('whatever result you want')
    process_smtp_message.return_value = f
    mock.assert_called_with(1, 2, 3)
然后


这可以用,但看起来很难看。还有其他类似python的方法吗?

模拟协同程序的另一种方法是生成返回mock的协同程序。通过这种方式,您可以模拟将被传递到
asyncio.wait
asyncio.wait\u for
的协同程序

这使得更通用的协同程序变得更加繁琐:

def make_coroutine(mock)
    async def coroutine(*args, **kwargs):
        return mock(*args, **kwargs)
    return coroutine


class Test(TestCase):
    def setUp(self):
        self.coroutine_mock = Mock()
        self.patcher = patch('some.coroutine',
                             new=make_coroutine(self.coroutine_mock))
        self.patcher.start()

    def tearDown(self):
        self.patcher.stop()

解决方案其实很简单: 我只需要将mock的
\uuuu call\uuu
方法转换为协同程序:

class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super(AsyncMock, self).__call__(*args, **kwargs)
这非常有效,当调用mock时,代码接收本机协同路由

用法示例:

@mock.patch('my.path.asyncio.sleep', new_callable=AsyncMock)
def test_stuff(sleep):
    # code

子类化
MagicMock
将为从协同程序mock生成的所有mock传播自定义类。例如,
AsyncMock()。\uuu str\uu
也将成为一个
AsyncMock
,这可能不是您想要的

相反,您可能希望定义一个工厂,该工厂创建一个带有自定义参数的
Mock
(或
MagicMock
),例如
side\u effect=coroutine(coro)
。另外,将协程函数与协程分离可能是一个好主意(如中所述)

以下是我的想法:

from asyncio import coroutine

def CoroMock():
    coro = Mock(name="CoroutineResult")
    corofunc = Mock(name="CoroutineFunction", side_effect=coroutine(coro))
    corofunc.coro = coro
    return corofunc
对不同对象的解释:

  • corofunc
    :coroutine函数mock
  • corofunc.side\u effect()
    :为每个调用生成的协同程序
  • corofunc.coro
    :协同程序用于获取结果的模拟
  • corofunc.coro.return\u值
    :协同程序返回的值
  • corofunc.coro.side\u effect
    :可能用于引发异常
例如:

async def coro(a, b):
    return await sleep(1, result=a+b)

def some_action(a, b):
    return get_event_loop().run_until_complete(coro(a, b))

@patch('__main__.coro', new_callable=CoroMock)
def test(corofunc):
    a, b, c = 1, 2, 3
    corofunc.coro.return_value = c
    result = some_action(a, b)
    corofunc.assert_called_with(a, b)
    assert result == c

每个人都缺少最简单、最清晰的解决方案:

class CoroutineMock(MagicMock):
    def __await__(self, *args, **kwargs):
        future = Future()
        future.set_result(self)
        result = yield from future
        return result
@patch('some.path')
def test(self, mock):
    f = asyncio.Future()
    f.set_result('whatever result you want')
    process_smtp_message.return_value = f
    mock.assert_called_with(1, 2, 3)

请记住,协同程序可以被视为一个函数,它保证返回一个未来,而未来可以等待。

基于@scolvin answer,我创建了这个(imo)更干净的方法:

import asyncio

def async_return(result):
    f = asyncio.Future()
    f.set_result(result)
    return f
就是这样,只需在想要异步的任何返回周围使用它,如

mock = MagicMock(return_value=async_return("Example return"))
await mock()
模拟异步对象的“最简单”解决方案的另一个变体,它只是一个线性程序

资料来源:

class Yo:
    async def foo(self):
        await self.bar()
    async def bar(self):
        # Some code
在测试中:

from asyncio import coroutine

yo = Yo()
# Here bounded method bar is mocked and will return a customised result.
yo.bar = Mock(side_effect=coroutine(lambda:'the awaitable should return this'))
event_loop.run_until_complete(yo.foo())

您可以这样设置异步方法的
返回值

mock=unittest.mock.MagicMock()
mock.your\u async\u method.return\u value=task\u from\u result(your\u return\u值)
异步定义任务\u来自\u结果(结果):
返回结果

调用方必须执行
等待您的\u async\u方法(..)
,就像该方法没有被模拟一样。

此外,由于asyncio.tasks,此模拟不能与asyncio.wait一起工作。请确保\u futuratris很好,但不能与autospec一起使用,autospec在使用MagicMock时基本上是强制性的。你有没有想过如何让它发挥作用?我对它的内部结构还不够熟悉……它对我来说非常适合。我是这样使用的:``@mock.patch('my.path.asyncio.sleep',new_callable=asynchmock,)def test_stuff(sleep):#code``这很有效。我最初喜欢下面Ivan Castellanos的另一个解决方案。但未来永远不会实现,我拼命尝试,但没有成功。这是最方便、最优雅的。至少用于基本用途。注意:
AsyncMock
unittest中可用。mock
从python 3.8开始,mock还自动检测应该使用它的位置(请参阅)。什么是进程\u smtp\u消息。返回\u值=f?另外,对正在测试的函数的调用在哪里?@Skorpeo-我认为他的意思是mock.return\u value=fI肯定不是。Mock是测试夹具。进程\u smtp\u消息显然是您试图模拟的内容。该解决方案与Python 3.8不兼容,Python 3.8引入了本机
AsyncMock
。因此,由于将来的类问题,带有解决方案的代码将失败并出现错误。但是一个带有简单的
AsyncMock
实现的Zozz解决方案可以在Python 3.7(例如)和Python 3.8中工作(如果您要有条件地导入本机
AsyncMock
),这不起作用,副作用=协同路由(coro),协同路由没有定义我实际上更像原始解决方案,因为它不需要对测试函数进行特殊重写。与问题中显示的方法相比,这种方法有什么优势?