unittest.mock.patch
を使用して、1つのネイティブコルーチンから別のコルーチンに非同期呼び出しをモックするにはどうすればよいですか?
私は現在非常に厄介なソリューションを持っています:
class CoroutineMock(MagicMock):
def __await__(self, *args, **kwargs):
future = Future()
future.set_result(self)
result = yield from future
return result
それから
class TestCoroutines(TestCase):
@patch('some.path', new_callable=CoroutineMock)
def test(self, mock):
some_action()
mock.assert_called_with(1,2,3)
これは機能しますが、見苦しいです。これを行うためのPython的な方法はありますか?
MagicMock
をサブクラス化すると、コルーチンモックから生成されたすべてのモックのカスタムクラスが伝播されます。たとえば、AsyncMock().__str__
もAsyncMock
になりますが、これはおそらく探しているものではありません。
代わりに、カスタム引数を持つMock
(またはMagicMock
)を作成するファクトリーを定義したい場合があります(例:side_effect=coroutine(coro)
)。また、コルーチン関数をコルーチンから分離することをお勧めします( documentation で説明されています)。
ここに私が思いついたものがあります:
_from asyncio import coroutine
def CoroMock():
coro = Mock(name="CoroutineResult")
corofunc = Mock(name="CoroutineFunction", side_effect=coroutine(coro))
corofunc.coro = coro
return corofunc
_
さまざまなオブジェクトの説明:
corofunc
:コルーチン関数のモックcorofunc.side_effect()
:呼び出しごとに生成されるコルーチンcorofunc.coro
_:結果を取得するためにコルーチンによって使用されるモックcorofunc.coro.return_value
_:コルーチンによって返された値corofunc.coro.side_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
_
おそらく最も簡単で明確な解決策は誰もが見逃しています:
@patch('some.path')
def test(self, mock):
f = asyncio.Future()
f.set_result('whatever result you want')
mock.return_value = f
mock.assert_called_with(1, 2, 3)
コルーチンは、未来を返すことが保証されている関数であると考えることができ、その関数は待たされる可能性があることを忘れないでください。
解決策は実際には非常に簡単でした。変換する必要があったのは__call__
コルーチンを模擬する方法:
class AsyncMock(MagicMock):
async def __call__(self, *args, **kwargs):
return super(AsyncMock, self).__call__(*args, **kwargs)
これは完全に機能します。モックが呼び出されると、コードはネイティブコルーチンを受け取ります。
使用例:
@mock.patch('my.path.asyncio.sleep', new_callable=AsyncMock)
def test_stuff(sleep):
# code
@scolvinの回答に基づいて、私はこの(imo)クリーナーな方法を作成しました:
def async_return(result):
f = asyncio.Future()
f.set_result(result)
return f
それだけです、あなたが非同期にしたい戻り値の周りでそれを使うだけです、
mock = MagicMock(return_value=async_return("Example return"))
await mock()
コルーチンをモックするもう1つの方法は、モルーチンを返すコルーチンを作成することです。この方法で、asyncio.wait
またはasyncio.wait_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()
非同期オブジェクトをモックするための「最も簡単な」ソリューションのもう1つのバリエーションは、1つのライナーです。
ソース内:
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())