Issue 37383: call count in not registered in AsyncMock till the coroutine is awaited
Created on 2019-06-24 10:30 by xtreak, last changed 2022-04-11 14:59 by admin. This issue is now closed.
| Pull Requests | |||
|---|---|---|---|
| URL | Status | Linked | Edit |
| PR 15761 | merged | lisroach, 2019-09-09 11:35 | |
| PR 15810 | merged | miss-islington, 2019-09-09 17:10 | |
| Messages (10) | |||
|---|---|---|---|
| msg346364 - (view) | Author: Karthikeyan Singaravelan (xtreak) * ![]() |
Date: 2019-06-24 10:30 | |
I noticed this while working on https://github.com/aio-libs/aiosmtpd/issues/167 where an async function was mocked that now returns an AsyncMock instead of MagicMock. The tests seem to look for call_args, mock_calls etc in the synchronous API without awaiting on the AsyncMock. In AsyncMock __call__ is an async function [0] and hence in the below example mock_calls is not recorded unless the coroutine is awaited. Is this intended since super()._mock_call [1] is inside the async function _mock_call through which the synchronous API is recorded. It's slightly confusing in my opinion while trying to use synchronous helpers before calling await. ./python.exe -m asyncio asyncio REPL 3.9.0a0 (heads/master:770847a7db, Jun 24 2019, 10:36:45) [Clang 7.0.2 (clang-700.1.81)] on darwin Use "await" directly instead of "asyncio.run()". Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> from unittest.mock import AsyncMock >>> mock = AsyncMock() >>> coro = mock(1, 2) >>> mock.mock_calls [] >>> await coro # Await executes _mock_call now and hence mock_calls are registered <AsyncMock name='mock()' id='4332060752'> >>> mock.mock_calls [call(1, 2)] [0] https://github.com/python/cpython/blob/47fbc4e45b35b3111e2d947a66490a43ac21d363/Lib/unittest/mock.py#L2081 [1] https://github.com/python/cpython/blob/47fbc4e45b35b3111e2d947a66490a43ac21d363/Lib/unittest/mock.py#L2083 |
|||
| msg351392 - (view) | Author: Lisa Roach (lisroach) * ![]() |
Date: 2019-09-09 09:55 | |
I see your point it is confusing the difference between the async and sync API, but I think the current AsyncMock call check is correct. According to the asyncio docs: "...simply calling a coroutine will not schedule it to be executed" (https://docs.python.org/3/library/asyncio-task.html#coroutines) and it goes on to say you either need to call the function with await, asyncio.run(), or asyncio.create_task(). So I believe calling an AsyncMock without using await should not log as a call, it is only if one of the three criteria above is met that it should be added to the mock_calls list. |
|||
| msg351407 - (view) | Author: Karthikeyan Singaravelan (xtreak) * ![]() |
Date: 2019-09-09 10:28 | |
I just checked the behavior with asynctest. The _mock_call implementation where the call is recorded is similar except that in asynctest it's a synchronous function [0] and in AsyncMock it's an async function [1] thus needs to be awaited to register call count. Agreed it's more of a confusion over does call mean a function call or something that should be recorded only once awaited given that there is call_count and await_calls. But would be good to have this documented that call_count is recorded only after await. Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21) [Clang 6.0 (clang-600.0.57)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import asynctest >>> m = asynctest.CoroutineMock() >>> m(1) <generator object CoroutineMock._mock_call.<locals>.proxy at 0x1023c8b88> >>> m.mock_calls [call(1)] Python 3.9.0a0 (heads/master:a6563650c8, Sep 9 2019, 14:53:16) [Clang 7.0.2 (clang-700.1.81)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from unittest.mock import AsyncMock >>> m = AsyncMock() >>> m.mock_calls [] [0] https://github.com/Martiusweb/asynctest/blob/d1d47ecb8220371284230d6d6fe642649ef82ab2/asynctest/mock.py#L584 [1] https://github.com/python/cpython/blob/19052a11314e7be7ba003fd6cdbb5400a5d77d96/Lib/unittest/mock.py#L2120 |
|||
| msg351412 - (view) | Author: Lisa Roach (lisroach) * ![]() |
Date: 2019-09-09 10:34 | |
Agreed, I think documentation can be clearer around this. I'll add a PR to try to clarify. |
|||
| msg351416 - (view) | Author: Lisa Roach (lisroach) * ![]() |
Date: 2019-09-09 10:38 | |
I wonder if `await_count` is really necessary, since it is essentially the same as `call_count`. Would it be too late or confusing to remove it now? |
|||
| msg351423 - (view) | Author: Karthikeyan Singaravelan (xtreak) * ![]() |
Date: 2019-09-09 10:50 | |
> I wonder if `await_count` is really necessary, since it is essentially the same as `call_count`. Would it be too late or confusing to remove it now? IMO if we are to document that mock_calls is recorded over await then we can have both call_count and await_count because if users start using AsyncMock then they can keep using the call_* functions. Removing it would mean there are also other counterparts as below to make sure we keep or remove all to remove discrepancy. I am more leaned towards keeping them and document it. >>> m.await m.await_args m.await_args_list m.await_count m.awaited >>> m.call m.call_args m.call_args_list m.call_count m.called With respect to removal I think the window is still open till the 3.8.0RC1. |
|||
| msg351428 - (view) | Author: Lisa Roach (lisroach) * ![]() |
Date: 2019-09-09 10:59 | |
I think you are right, I'd prefer to leave it in. It also helps users who are switching from AsyncTest to the std lib AsyncMock, they can keep using `await_count`. I'll update the docs! |
|||
| msg351533 - (view) | Author: Lisa Roach (lisroach) * ![]() |
Date: 2019-09-09 16:54 | |
New changeset b9f65f01fd761da7799f36d29b54518399d3458e by Lisa Roach in branch 'master': bpo-37383: Updates docs to reflect AsyncMock call_count after await. (#15761) https://github.com/python/cpython/commit/b9f65f01fd761da7799f36d29b54518399d3458e |
|||
| msg351584 - (view) | Author: miss-islington (miss-islington) | Date: 2019-09-10 07:31 | |
New changeset d4391aa5eb4767e19b7b380a836413e7c47f1fb4 by Miss Islington (bot) in branch '3.8': bpo-37383: Updates docs to reflect AsyncMock call_count after await. (GH-15761) https://github.com/python/cpython/commit/d4391aa5eb4767e19b7b380a836413e7c47f1fb4 |
|||
| msg351592 - (view) | Author: Karthikeyan Singaravelan (xtreak) * ![]() |
Date: 2019-09-10 08:50 | |
Closing since docs are updated. Thank you Lisa. |
|||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022-04-11 14:59:17 | admin | set | github: 81564 |
| 2019-09-10 08:50:44 | xtreak | set | status: open -> closed resolution: fixed messages: + msg351592 stage: patch review -> resolved |
| 2019-09-10 07:31:38 | miss-islington | set | nosy:
+ miss-islington messages: + msg351584 |
| 2019-09-09 17:10:57 | miss-islington | set | pull_requests: + pull_request15460 |
| 2019-09-09 16:54:16 | lisroach | set | messages: + msg351533 |
| 2019-09-09 11:35:33 | lisroach | set | keywords:
+ patch stage: patch review pull_requests: + pull_request15414 |
| 2019-09-09 10:59:04 | lisroach | set | messages: + msg351428 |
| 2019-09-09 10:50:23 | xtreak | set | messages: + msg351423 |
| 2019-09-09 10:38:15 | lisroach | set | messages: + msg351416 |
| 2019-09-09 10:34:52 | lisroach | set | messages: + msg351412 |
| 2019-09-09 10:28:02 | xtreak | set | messages: + msg351407 |
| 2019-09-09 09:55:22 | lisroach | set | messages: + msg351392 |
| 2019-06-24 10:30:16 | xtreak | create | |
