Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - object MagicMock can't be used in 'await' expression

When I was trying to mock an async function in unittest with MagicMock, I got this exception:

TypeError: object MagicMock can't be used in 'await' expression

With sample code like:

# source code
class Service:
    async def compute(self, x):
        return x

class App:
    def __init__(self):
        self.service = Service()

    async def handle(self, x):
        return await self.service.compute(x)

# test code
import asyncio
import unittest
from unittest.mock import patch


class TestApp(unittest.TestCase):
    @patch('__main__.Service')
    def test_handle(self, mock):
        loop = asyncio.get_event_loop()
        app = App()
        res = loop.run_until_complete(app.handle('foo'))
        app.service.compute.assert_called_with("foo")

if __name__ == '__main__':
    unittest.main()

How should I fix it with built-in python3 libraries?

like image 276
shaun shia Avatar asked Jul 18 '18 05:07

shaun shia


3 Answers

I ended up with this hack.

# monkey patch MagicMock
async def async_magic():
    pass

MagicMock.__await__ = lambda x: async_magic().__await__()

It only works for MagicMock, not other pre-defined return_value

like image 177
shaun shia Avatar answered Nov 19 '22 04:11

shaun shia


You can get mocks to return objects that can be awaited by using a Future. The following is a pytest test case, but something similar should be possible with unittest.

async def test_that_mock_can_be_awaited():
    mock = MagicMock(return_value=Future())
    mock.return_value.set_result(123)
    result = await mock()
    assert result == 123

In your case, since you're patching Service (which gets passed in as mock), mock.return_value = Future() should do the trick.

like image 27
z0r Avatar answered Nov 19 '22 04:11

z0r


In python 3.8+ you can make use of the AsyncMock

async def test_that_mock_can_be_awaited():
   mock = AsyncMock()
   mock.x.return_value = 123
   result = await mock.x()
   assert result == 123

The class AsyncMock object will behave so the object is recognized as an async function, and the result of a call is an awaitable.

>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
>>> inspect.isawaitable(mock())
True
like image 26
Thulani Chivandikwa Avatar answered Nov 19 '22 05:11

Thulani Chivandikwa