我使用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
函数没有根据{{1}}电话嘲笑。 (特别是它说“无法解析来自URL'foo'的主机名”,如果未被模拟,它就应该这样做。)
我无法逃避这种行为。我试过了:
patch
。is_ok
和mock_client
,将mock_client.__aenter__
设置为mock_client.request
,或使用MagicMock(return_value=mock_response)
等的各种组合。mock_client().request
和ClientSession
方法编写模拟__aenter__
,并在__aexit__
new
参数中使用它。这些似乎没有任何区别。如果我将断言放入patch
来测试is_ok
是ClientSession
的实例,那么当我运行测试时这些断言就会失败(同样,当代码没有打补丁时它们也会失败) )。这导致了我的命名空间不匹配理论:即,事件循环在MagicMock
所针对的不同命名空间中运行。
要不然,或者我做了些蠢事!