-
Rate limit · GitHub Access has been restricted
You have triggered a rate limit.
Please wait a few minutes before you try again;
in some cases this may take up to an hour. -
Notifications
You must be signed in to change notification settings - Fork 31.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-38093: Correctly returns AsyncMock for async subclasses. #15947
Access has been restricted
You have triggered a rate limit.
Please wait a few minutes before you try again;
in some cases this may take up to an hour.
Changes from 1 commit
7fbd8fa
8ef74fe
d3f2706
bd02a3a
d656592
b831e58
5b3f96a
b4e164c
14e684e
f476b58
0d3af26
7d9dfa0
d671277
bb57b0c
8bc3e32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Access has been restricted
You have triggered a rate limit.
Please wait a few minutes before you try again;
in some cases this may take up to an hour.
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
|
||
.. testsetup:: | ||
|
||
import asyncio | ||
import unittest | ||
from unittest.mock import Mock, MagicMock, patch, call, sentinel | ||
|
||
|
@@ -276,6 +277,45 @@ function returns is what the call returns: | |
2 | ||
|
||
|
||
Mocking asynchronous iterators | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Since Python 3.8, ``AsyncMock`` has support to mock :ref:`async-iterators` | ||
through ``__aiter__``. The :attr:`~Mock.return_value` attribute of ``__aiter__`` | ||
can be used to set the return values to be used for iteration. | ||
|
||
>>> mock = AsyncMock() | ||
>>> mock.__aiter__.return_value = [1, 2, 3] | ||
>>> async def main(): | ||
... return [i async for i in mock] | ||
... | ||
>>> asyncio.run(main()) | ||
[1, 2, 3] | ||
|
||
|
||
Mocking asynchronous context manager | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Since Python 3.8, ``AsyncMock`` has support to mock | ||
:ref:`async-context-managers` through ``__aenter__`` and ``__aexit__``. The | ||
return value of ``__aenter__`` is an async function. | ||
|
||
>>> class AsyncContextManager: | ||
... async def __aenter__(self): | ||
... return self | ||
... async def __aexit__(self, exc_type, exc, tb): | ||
... pass | ||
... | ||
>>> mock_instance = AsyncMock(AsyncContextManager()) | ||
>>> async def main(): | ||
... async with mock_instance as result: | ||
... pass | ||
... | ||
>>> asyncio.run(main()) | ||
>>> mock_instance.__aenter__.assert_called_once() | ||
>>> mock_instance.__aexit__.assert_called_once() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eventually we probably want to replace these either with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, I will update them in this PR if that's okay. |
||
|
||
|
||
Creating a Mock from an Existing Object | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -989,6 +989,9 @@ def _get_child_mock(self, /, **kw): | |
_type = type(self) | ||
if issubclass(_type, MagicMock) and _new_name in _async_method_magics: | ||
klass = AsyncMock | ||
elif _new_name in _sync_async_magics: | ||
# Special case these ones b/c users will assume they are async, but they are actually sync | ||
klass = MagicMock | ||
elif issubclass(_type, AsyncMockMixin): | ||
klass = AsyncMock | ||
elif not issubclass(_type, CallableMixin): | ||
|
@@ -1868,7 +1871,7 @@ def _patch_stopall(): | |
'__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', | ||
'__getstate__', '__setstate__', '__getformat__', '__setformat__', | ||
'__repr__', '__dir__', '__subclasses__', '__format__', | ||
'__getnewargs_ex__', '__aenter__', '__aexit__', '__anext__', '__aiter__', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have a test for the default values of these functions? There is a behavior change in 3.8b4 and now where
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm this is a good point about This section specifically is the "non-default" values, so I don't think it make sense to update that section of the docs, and there is a unit test for the default values that are set in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah well, this is actually another case of "called" vs "awaited". Once you await So I want to leave the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah okay so it's a case where once There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, this is how async cms work: https://www.python.org/dev/peps/pep-0492/#new-syntax |
||
'__getnewargs_ex__', | ||
} | ||
|
||
|
||
|
@@ -1887,10 +1890,12 @@ def method(self, /, *args, **kw): | |
|
||
# Magic methods used for async `with` statements | ||
_async_method_magics = {"__aenter__", "__aexit__", "__anext__"} | ||
# `__aiter__` is a plain function but used with async calls | ||
_async_magics = _async_method_magics | {"__aiter__"} | ||
# Magic methods that are only used with async calls but are synchronous functions themselves | ||
_sync_async_magics = {"__aiter__"} | ||
_async_magics = _async_method_magics | _sync_async_magics | ||
|
||
_all_magics = _magics | _non_defaults | ||
_all_sync_magics = _magics | _non_defaults | ||
_all_magics = _all_sync_magics | _async_magics | ||
|
||
_unsupported_magics = { | ||
'__getattr__', '__setattr__', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to say something like:
We want to make the distinction between what the methods are and what they return clear.