How do I mock async call from one native coroutine to other one using unittest.mock.patch
?
I currently have quite an awkward solution:
class CoroutineMock(MagicMock):
def __await__(self, *args, **kwargs):
future = Future()
future.set_result(self)
result = yield from future
return result
Then
class TestCoroutines(TestCase):
@patch('some.path', new_callable=CoroutineMock)
def test(self, mock):
some_action()
mock.assert_called_with(1,2,3)
This works but looks ugly. Is there more pythonic way to do this?
Based on @scolvin answer I created this (imo) cleaner way:
That's it, just use it around whatever return you want to be async, as in
Everyone's missing what's probably the simplest and clearest solution:
remember a coroutine can be thought of as just a function which is guaranteed to return a future which can, in turn be awaited.
The solution was actually quite simple: I just needed to convert
__call__
method of mock into coroutine:This works perfectly, when mock is called, code receives native coroutine
One more variant of "simplest" solution to mock a async object, which is just a one liner.
In source:
In test:
Subclassing
MagicMock
will propagate your custom class for all the mocks generated from your coroutine mock. For instance,AsyncMock().__str__
will also become anAsyncMock
which is probably not what you're looking for.Instead, you might want to define a factory that creates a
Mock
(or aMagicMock
) with custom arguments, for instanceside_effect=coroutine(coro)
. Also, it might be a good idea to separate the coroutine function from the coroutine (as explained in the documentation).Here is what I came up with:
An explanation of the different objects:
corofunc
: the coroutine function mockcorofunc.side_effect()
: the coroutine, generated for each callcorofunc.coro
: the mock used by the coroutine to get the resultcorofunc.coro.return_value
: the value returned by the coroutinecorofunc.coro.side_effect
: might be used to raise an exceptionExample:
Another way of mocking coroutine is to make coroutine, that returns mock. This way you can mock coroutines that will be passed into
asyncio.wait
orasyncio.wait_for
.This makes more universal coroutines though makes setup of tests more cumbersome: