【发布时间】:2017-09-27 14:30:59
【问题描述】:
我使用 aiohttp 编写了一个简单的 HTTP 客户端,我正在尝试通过修补 aiohttp.ClientSession 和 aiohttp.ClientResponse 来测试它。但是,似乎unittest.mock.patch 装饰器不尊重我的异步代码。猜测一下,我会说这是某种命名空间不匹配。
这是一个最小的例子:
from aiohttp import ClientSession
async def is_ok(url:str) -> bool:
async with ClientSession() as session:
async with session.request("GET", url) as response:
return (response.status == 200)
我正在使用异步装饰器进行测试,如this answer 中所述。所以这是我尝试的测试:
import unittest
from unittest.mock import MagicMock, patch
from aiohttp import ClientResponse
from my.original.module import is_ok
class TestClient(unittest.TestCase):
@async_test
@patch("my.original.module.ClientSession", spec=True)
async def test_client(self, mock_client):
mock_response = MagicMock(spec=ClientResponse)
mock_response.status = 200
async def _mock_request(*args, **kwargs):
return mock_response
mock_client.request = mock_response
status = await is_ok("foo")
self.assertTrue(status)
我的is_ok 协程在用于__main__ 时运行良好,但是当我运行测试时,它给了我一个错误,表明session.request 函数没有被我的patch 模拟称呼。 (具体来说,它说“无法从 URL 'foo' 解析主机名”,如果它没有被模拟,它应该这样做。)
我无法逃避这种行为。我试过了:
- 模拟完成后导入
is_ok。 - 将模拟分配给
mock_client和mock_client.__aenter__,将mock_client.request设置为MagicMock(return_value=mock_response),或使用mock_client().request等的各种组合。 - 使用特定的
__aenter__和__aexit__方法编写模拟ClientSession,并在new参数中使用它patch。
这些似乎都没有影响。如果我将断言放入is_ok 以测试ClientSession 是MagicMock 的一个实例,那么这些断言在我运行测试时会失败(同样,当代码未修补时它们也会失败)。这引出了我的命名空间不匹配理论:也就是说,事件循环在 patch 所针对的不同命名空间中运行。
要么这样,要么我在做一些愚蠢的事情!
【问题讨论】:
标签: python testing patch python-asyncio aiohttp