This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: MagicMock __aenter__ should be AsyncMock(return_value=MagicMock())
Type: behavior Stage:
Components: asyncio, Tests Versions: Python 3.9, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, brian.curtin, graingert, lisroach, simon.d.beal, xtreak, yselivanov
Priority: normal Keywords:

Created on 2020-04-27 13:33 by graingert, last changed 2022-04-11 14:59 by admin.

Messages (4)
msg367419 - (view) Author: Thomas Grainger (graingert) * Date: 2020-04-27 13:33
aentering a MagicMock() results in an AsyncMock which behaves differently than I expected:

```
python3.9 -m asyncio
asyncio REPL 3.9.0a5 (default, Apr 18 2020, 00:00:31) 
[GCC 9.3.0] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> from unittest import mock
>>> with mock.MagicMock() as m:
...     with m.foo() as u:
...         u.hello()
... 
<MagicMock name='mock.__enter__().foo().__enter__().hello()' id='140670894620048'>
>>> async with mock.MagicMock() as m:
...     async with m.foo() as u:
...         u.hello()
... 
<console>:2: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Traceback (most recent call last):
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 439, in result
    return self.__get_result()
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 388, in __get_result
    raise self._exception
  File "<console>", line 2, in <module>
AttributeError: __aenter__
```

This is annoying for mocking database interfaces like

```
async def update_users(db, user):
    async with db.connection() as conn:
        async with conn.transaction() as tx:
            ...
```
msg367420 - (view) Author: Thomas Grainger (graingert) * Date: 2020-04-27 13:34
Perhaps there could be a MagicAsyncMock that supports .__await__ and .__aenter__ etc?
msg367903 - (view) Author: Brian Curtin (brian.curtin) * (Python committer) Date: 2020-05-02 00:13
graingert: Do you have a workaround for this? I'm doing roughly the same thing with an asyncpg connection pool nested with a transaction and am getting nowhere.


    async with pg_pool.acquire() as conn:
        async with conn.transaction():
            ...
msg367913 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2020-05-02 04:22
MagicMock object on call returns another MagicMock. AsyncMock object in turn returns a coroutine which has to be awaited. 

In the report mock.MagicMock().__aenter__() returns an AsyncMock object. Accessing the query attribute will create an AsyncMock object. Calling query() will return a coroutine which will not have a __aexit__. Mock supports configure_mock through which nested calls can be mocked but the return_value has to be accessed to mock the nested attributes. So a workaround could be below. The connection object is mocked such that the __aenter__ returns a MagicMock. That magic mock can have the query attribute to be mocked whose return_value is an AsyncMock if the the query object has to awaited.

See also issue37052 regarding adding an example. See also https://github.com/python/cpython/pull/16859 regarding adding an example along similar database mocking that will help.

>>> from unittest.mock import MagicMock, AsyncMock
<MagicMock name='mock()' id='140285187747744'>
>>> AsyncMock()()
<coroutine object AsyncMockMixin._mock_call at 0x7f96b0c5f3c0>


import asyncio
from unittest.mock import MagicMock, AsyncMock, patch


class Database:
    pass


async def mock_database():
    with patch(f"{__name__}.Database") as db:
        db.configure_mock(
            **{
                "connection.return_value.__aenter__.return_value": MagicMock(
                    **{
                        "query.return_value.__aenter__.return_value": AsyncMock(
                            return_value=[1]
                        )
                    }
                )
            }
        )

        async with db.connection() as conn:
            async with conn.query() as query:
                result = await query("select * from people")
                assert result == [1]
                print(f"Result : {result}")


asyncio.run(mock_database())


Hope it helps.
History
Date User Action Args
2022-04-11 14:59:29adminsetgithub: 84586
2020-12-07 23:23:01simon.d.bealsetnosy: + simon.d.beal
2020-05-02 04:22:07xtreaksetmessages: + msg367913
2020-05-02 00:13:33brian.curtinsetnosy: + brian.curtin
messages: + msg367903
2020-04-27 14:07:09xtreaksetnosy: + lisroach, xtreak
type: behavior
2020-04-27 13:34:16graingertsetmessages: + msg367420
2020-04-27 13:33:28graingertcreate