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

前端 未结 5 1154
伪装坚强ぢ
伪装坚强ぢ 2021-02-18 19:49

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\' expr

相关标签:
5条回答
  • 2021-02-18 20:21

    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

    0 讨论(0)
  • 2021-02-18 20:23

    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
    
    0 讨论(0)
  • 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

    0 讨论(0)
  • 2021-02-18 20:37

    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.

    0 讨论(0)
  • 2021-02-18 20:42

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

    0 讨论(0)
提交回复
热议问题