shaun shia
shaun shia

Reputation: 1172

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?

Upvotes: 55

Views: 54061

Answers (8)

mecampbellsoup
mecampbellsoup

Reputation: 1501

I came upon this SO discussion when I encountered errors like the following in my own code/test code:

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

My code under test was asynchronous Django ORM queries where I wanted to mock the ultimate return value of some DB lookup, e.g.:

try:
    user = await AllauthUser.objects.aget(email=email)
except AllauthUser.DoesNotExist:
    # handle user not found

Ultimately, I needed to simply add the following kwarg to my mock object initialization: new_callable=AsyncMock. Docs can be seen here.

I'm sharing this answer because I spent more time than I'd care to admit banging my head against these type errors!

Upvotes: 0

Danny Sepler
Danny Sepler

Reputation: 59

If you'd like a pytest-mock solution compatible with < py3.8, I did something like this.

class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs)

def test_my_method(mocker):
    my_mock = mocker.patch("path.to.mocked.thing", AsyncMock())
    my_mock.return_value = [1, 2, 3]

    assert my_method()

Definitely borrowed from Tomasz's solution here!

Upvotes: 1

rnstlr
rnstlr

Reputation: 1803

To override async classes one needs to tell patch that the return_value needs to be AsyncMock. So use

@patch('__main__.Service', return_value=AsyncMock(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")

With that Service will be a MagicMock, but Service() will return an AsyncMock instance of Service.

Upvotes: 0

Tomasz Bartkowiak
Tomasz Bartkowiak

Reputation: 14988

I found this comment very useful when trying to await a mock object in Python < 3.8. You simply create a child class AsyncMock that inherits from MagicMock and overwrites a __call__ method to be a coroutine:

class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super(AsyncMock, self).__call__(*args, **kwargs)

Then, inside your test, do:

@pytest.mark.asyncio
async def test_my_method():
    # Test "my_method" coroutine by injecting an async mock
    my_mock = AsyncMock()
    assert await my_method(my_mock)

you might also want to install pytest-asyncio

Upvotes: 6

Thulani Chivandikwa
Thulani Chivandikwa

Reputation: 3539

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

Upvotes: 41

mani
mani

Reputation: 307

shaun shia provided really good universal solution, but i found what in python 3.8 you can use just @patch('__main__.Service', new=AsyncMock)

Upvotes: 14

z0r
z0r

Reputation: 8585

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.

Upvotes: 13

shaun shia
shaun shia

Reputation: 1172

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

Upvotes: 28

Related Questions