Python asyncio:如何模拟 __aiter__() 方法?

新手上路,请多包涵

我有一个代码正在使用 aiohttp 监听 WebSocket 上的消息。

看起来像:

 async for msg in ws:
    await self._ws_msg_handler.handle_message(ws, msg, _services)

其中 wsaiohttp.web.WebSocketResponse() 的实例( 原始代码

在我的测试中,我模拟了 WebSocketResponse() 及其 __aiter__ 方法:

 def coro_mock(**kwargs):
    return asyncio.coroutine(mock.Mock(**kwargs))

@pytest.mark.asyncio
@mock.patch('aiojsonrpc.request_handler.WebSocketMessageHandler')
async def test_rpc_websocket_handler(
    MockWebSocketMessageHandler,
    rpc_websocket_handler
):

    ws_response = 'aiojsonrpc.request_handler.WebSocketResponse'
    with mock.patch(ws_response) as MockWebSocketResponse:
        MockRequest = mock.MagicMock()
        req = MockRequest()

        ws_instance = MockWebSocketResponse.return_value
        ws_instance.prepare = coro_mock()
        ws_instance.__aiter__ = coro_mock(return_value=iter(range(5)))
        ws_instance.__anext__ = coro_mock()

        handle_msg_result = 'Message processed'
        MockWebSocketMessageHandler.handle_message.side_effect = Exception(
            handle_msg_result)
        msg_handler = MockWebSocketMessageHandler()

        with pytest.raises(Exception) as e:
            await request_handler.RpcWebsocketHandler(msg_handler)(req)
        assert str(e.value) == handle_msg_result

虽然当我运行 测试 时它失败并显示错误消息:

‘async for’ 需要一个带有 __aiter__ 方法的对象,得到 MagicMock

 =================================================================================== FAILURES ===================================================================================
__________________________________________________________________________ test_rpc_websocket_handler __________________________________________________________________________

MockWebSocketMessageHandler = <MagicMock name='WebSocketMessageHandler' id='140687969989632'>
rpc_websocket_handler = <aiojsonrpc.request_handler.RpcWebsocketHandler object at 0x7ff47879b0f0>

    @pytest.mark.asyncio
    @mock.patch('aiojsonrpc.request_handler.WebSocketMessageHandler')
    async def test_rpc_websocket_handler(
        MockWebSocketMessageHandler,
        rpc_websocket_handler
    ):

        ws_response = 'aiojsonrpc.request_handler.WebSocketResponse'
        with mock.patch(ws_response) as MockWebSocketResponse:
            # MockRequest = mock.create_autospec(aiohttp.web_reqrep.Request)
            # req = MockRequest(*[None] * 6)
            MockRequest = mock.MagicMock()
            req = MockRequest()

            ws_instance = MockWebSocketResponse.return_value
            ret = mock.Mock()
            ws_instance.prepare = coro_mock()
            ws_instance.__aiter__ = coro_mock(return_value=iter(range(5)))
            ws_instance.__anext__ = coro_mock()

            handle_msg_result = 'Message processed'
            MockWebSocketMessageHandler.handle_message.side_effect = Exception(
                handle_msg_result)
            msg_handler = MockWebSocketMessageHandler()

            with pytest.raises(Exception) as e:
                await request_handler.RpcWebsocketHandler(msg_handler)(req)
>           assert str(e.value) == handle_msg_result
E           assert "'async for' ...got MagicMock" == 'Message processed'
E             - 'async for' requires an object with __aiter__ method, got MagicMock
E             + Message processed

tests/test_request_handler.py:252: AssertionError

所以它的行为就像 __aiter__() 从未被嘲笑过。在这种情况下我应该如何完成正确的模拟?


更新:

现在我已经找到了一种使代码可测试的 解决方法,但如果有人告诉我如何处理原始问题中描述的问题,我将不胜感激。

原文由 Eugene Naydenov 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 732
2 个回答

您可以使模拟类返回一个实现预期接口的对象:

 class AsyncIterator:
    def __init__(self, seq):
        self.iter = iter(seq)

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            return next(self.iter)
        except StopIteration:
            raise StopAsyncIteration

MockWebSocketResponse.return_value = AsyncIterator(range(5))

我不认为有一种方法(还)可以正确地模拟一个对象实现 __aiter__ ,它可能是一个 python 错误,因为 async for 拒绝 MagicMock ,即使 hasattr(the_magic_mock, '__aiter__')True

编辑(13/12/2017) :自 0.11 以来,库 asynctest 支持异步迭代器和上下文管理器,asynctest.MagicMock 免费提供此功能。

原文由 Martin Richard 发布,翻译遵循 CC BY-SA 4.0 许可协议

对于后代,我有同样的问题需要测试 async for 循环,但公认的解决方案似乎不适用于 Python 3.7。下面的示例适用于 3.6.x3.7.0 ,但 不适 用于 3.5.x

 import asyncio

class AsyncIter:
    def __init__(self, items):
        self.items = items

    async def __aiter__(self):
        for item in self.items:
            yield item

async def print_iter(items):
    async for item in items:
        print(item)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    things = AsyncIter([1, 2, 3])
    loop.run_until_complete(print_iter(things))
    loop.close()

通过以上内容,模拟它看起来像:

 with mock.patch('some.async.iter', return_value=AsyncIter([1, 2, 3])):
  # do test requiring mocked iter

原文由 Lex Scarisbrick 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进