-
-
Notifications
You must be signed in to change notification settings - Fork 32k
bpo-26467: Adds AsyncMock for asyncio Mock library support #9296
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
Changes from 1 commit
4353041
b83e5a5
a9ea983
50581e3
96ddb0e
a4d4dbc
bfdd5a7
ed7f13c
34fa74e
302ef64
bf749ac
30b64b5
5edac2a
fa978cc
24920a6
aec3153
45dddb7
c0a88a9
f9bee6e
81ad0d1
c260104
ae13db1
68dff1b
64301e2
c7cd95e
033f7d3
2fef02c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -201,9 +201,11 @@ The Mock Class | |||||
|
||||||
.. testsetup:: | ||||||
|
||||||
import asyncio | ||||||
import inspect | ||||||
import unittest | ||||||
from unittest.mock import sentinel, DEFAULT, ANY | ||||||
from unittest.mock import patch, call, Mock, MagicMock, PropertyMock | ||||||
from unittest.mock import patch, call, Mock, MagicMock, PropertyMock, AsyncMock | ||||||
from unittest.mock import mock_open | ||||||
|
||||||
:class:`Mock` is a flexible mock object intended to replace the use of stubs and | ||||||
|
@@ -885,9 +887,9 @@ object:: | |||||
... | ||||||
>>> mock = MagicMock(async_func) | ||||||
>>> mock | ||||||
<MagicMock spec='function' id='4568403696'> | ||||||
<MagicMock spec='function' id='...'> | ||||||
>>> mock() | ||||||
<coroutine object AsyncMockMixin._mock_call at 0x1104cb440> | ||||||
<coroutine object AsyncMockMixin._mock_call at ...> | ||||||
|
||||||
.. method:: assert_awaited() | ||||||
|
||||||
|
@@ -976,11 +978,11 @@ object:: | |||||
Assert the mock has been awaited with the specified calls. | ||||||
The :attr:`await_args_list` list is checked for the awaits. | ||||||
|
||||||
If `any_order` is False (the default) then the awaits must be | ||||||
If *any_order* is False (the default) then the awaits must be | ||||||
sequential. There can be extra calls before or after the | ||||||
specified awaits. | ||||||
|
||||||
If `any_order` is True then the awaits can be in any order, but | ||||||
If *any_order* is True then the awaits can be in any order, but | ||||||
they must all appear in :attr:`await_args_list`. | ||||||
|
||||||
>>> mock = AsyncMock() | ||||||
|
@@ -1009,6 +1011,58 @@ object:: | |||||
See :func:`Mock.reset_mock`. Also sets :attr:`await_count` to 0, | ||||||
:attr:`await_args` to None, and clears the :attr:`await_args_list`. | ||||||
|
||||||
.. attribute:: await_count | ||||||
|
||||||
An integer keeping track of how many times the mock object has been awaited. | ||||||
|
||||||
>>> mock = AsyncMock() | ||||||
>>> async def main(): | ||||||
... await mock() | ||||||
... | ||||||
>>> asyncio.run(main()) | ||||||
>>> mock.await_count | ||||||
1 | ||||||
>>> asyncio.run(main()) | ||||||
>>> mock.await_count | ||||||
2 | ||||||
|
||||||
.. attribute:: await_args | ||||||
|
||||||
This is either ``None`` (if the mock hasn’t been awaited), or the arguments that | ||||||
the mock was last awaited with. Functions the same as :attr:`Mock.call_args`. | ||||||
|
||||||
>>> mock = AsyncMock() | ||||||
>>> async def main(*args): | ||||||
... await mock() | ||||||
... | ||||||
>>> mock.await_args | ||||||
>>> asyncio.run(main('foo')) | ||||||
>>> mock.await_args | ||||||
call('foo') | ||||||
>>> asyncio.run(main('bar')) | ||||||
>>> mock.await_args | ||||||
call('bar') | ||||||
|
||||||
|
||||||
.. attribute:: await_args_list | ||||||
|
||||||
This is a list of all the awaits made to the mock object in sequence (so the | ||||||
length of the list is the number of times it has been awaited). Before any | ||||||
awaits have been made it is an empty list. | ||||||
|
||||||
>>> mock = AsyncMock() | ||||||
>>> async def main(*args): | ||||||
... await mock() | ||||||
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.
Suggested change
|
||||||
... | ||||||
>>> mock.await_args_list | ||||||
[] | ||||||
>>> asyncio.run(main('foo')) | ||||||
>>> mock.await_args | ||||||
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.
Suggested change
|
||||||
[call('foo')] | ||||||
>>> asyncio.run(main('bar')) | ||||||
>>> mock.await_args_list | ||||||
[call('foo'), call('bar')] | ||||||
|
||||||
|
||||||
Calling | ||||||
~~~~~~~ | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2429,7 +2429,6 @@ def call_list(self): | |
call = _Call(from_kall=False) | ||
|
||
|
||
|
||
def create_autospec(spec, spec_set=False, instance=False, _parent=None, | ||
_name=None, **kwargs): | ||
"""Create a mock object using another object as a spec. Attributes on the | ||
|
@@ -2476,9 +2475,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, | |
# because we don't know what type they return | ||
_kwargs = {} | ||
elif is_async_func: | ||
if instance: | ||
raise RuntimeError("Instance can not be True when create_autospec " | ||
"is mocking an async function") | ||
# if instance: | ||
# raise RuntimeError("Instance can not be True when create_autospec " | ||
# "is mocking an async function") | ||
Klass = AsyncMock | ||
elif not _callable(spec): | ||
Klass = NonCallableMagicMock | ||
|
@@ -2501,7 +2500,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, | |
# recurse for functions | ||
mock = _set_signature(mock, spec) | ||
if is_async_func: | ||
mock._is_coroutine = _is_coroutine | ||
mock._is_coroutine = asyncio.coroutines._is_coroutine | ||
mock.await_count = 0 | ||
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. Could this be documented as an attribute? I can see a reference 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. +1. |
||
mock.await_args = None | ||
mock.await_args_list = _CallList() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,12 @@ | |
import inspect | ||
import unittest | ||
|
||
from unittest.mock import call, AsyncMock, patch, MagicMock | ||
from unittest.mock import call, AsyncMock, patch, MagicMock, create_autospec | ||
|
||
|
||
def tearDownModule(): | ||
asyncio.set_event_loop_policy(None) | ||
|
||
|
||
class AsyncClass: | ||
def __init__(self): | ||
|
@@ -28,14 +33,6 @@ def a(self): | |
|
||
|
||
class AsyncPatchDecoratorTest(unittest.TestCase): | ||
def setUp(self): | ||
# Prevents altering the execution environment | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
def test_is_coroutine_function_patch(self): | ||
@patch.object(AsyncClass, 'async_method') | ||
def test_async(mock_method): | ||
|
@@ -61,19 +58,12 @@ def test_no_parent_attribute(mock_method): | |
def test_is_AsyncMock_patch(self): | ||
@patch.object(AsyncClass, 'async_method') | ||
def test_async(mock_method): | ||
self.assertTrue(isinstance(mock_method, AsyncMock)) | ||
self.assertIsInstance(mock_method, AsyncMock) | ||
|
||
test_async() | ||
|
||
|
||
class AsyncPatchCMTest(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
def test_is_async_function_cm(self): | ||
def test_async(): | ||
with patch.object(AsyncClass, 'async_method') as mock_method: | ||
|
@@ -93,19 +83,12 @@ def test_async(): | |
def test_is_AsyncMock_cm(self): | ||
def test_async(): | ||
with patch.object(AsyncClass, 'async_method') as mock_method: | ||
self.assertTrue(isinstance(mock_method, AsyncMock)) | ||
self.assertIsInstance(mock_method, AsyncMock) | ||
|
||
test_async() | ||
|
||
|
||
class AsyncMockTest(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
def test_iscoroutinefunction_default(self): | ||
mock = AsyncMock() | ||
self.assertTrue(asyncio.iscoroutinefunction(mock)) | ||
|
@@ -130,37 +113,39 @@ def foo(): pass | |
self.assertTrue(inspect.iscoroutinefunction(mock)) | ||
|
||
def test_future_isfuture(self): | ||
mock = AsyncMock(asyncio.Future()) | ||
loop = asyncio.new_event_loop() | ||
asyncio.set_event_loop(loop) | ||
fut = asyncio.Future() | ||
loop.stop() | ||
loop.close() | ||
mock = AsyncMock(fut) | ||
self.assertIsInstance(mock, asyncio.Future) | ||
|
||
|
||
class AsyncAutospecTest(unittest.TestCase): | ||
def test_is_AsyncMock_patch(self): | ||
@patch(async_foo_name, autospec=True) | ||
def test_async(mock_method): | ||
self.assertTrue(isinstance( | ||
mock_method.async_method, | ||
AsyncMock)) | ||
self.assertTrue(isinstance(mock_method, MagicMock)) | ||
self.assertIsInstance(mock_method.async_method, AsyncMock) | ||
self.assertIsInstance(mock_method, MagicMock) | ||
|
||
@patch(async_foo_name, autospec=True) | ||
def test_normal_method(mock_method): | ||
self.assertTrue(isinstance( | ||
mock_method.normal_method, | ||
MagicMock)) | ||
self.assertIsInstance(mock_method.normal_method, MagicMock) | ||
|
||
test_async() | ||
test_normal_method() | ||
|
||
def test_create_autospec_instance(self): | ||
with self.assertRaises(RuntimeError): | ||
create_autospec(async_func, instance=True) | ||
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. This test fails since 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 yeah, sorry I was trying to remember the answer to your question on why it was needed. It might not be and I'm debating about taking it out, I think I don't fully understand the purpose of instance=True and what we would want the behavior to be if it is True with create_autospec on a async function. 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. No problem, we can remove the restriction later if needed. I have added one more doctest suggestion that would make the CI green and this can be merged. |
||
|
||
class AsyncSpecTest(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
def test_create_autospec(self): | ||
spec = create_autospec(async_func) | ||
self.assertTrue(asyncio.iscoroutinefunction(spec)) | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
class AsyncSpecTest(unittest.TestCase): | ||
def test_spec_as_async_positional_magicmock(self): | ||
mock = MagicMock(async_func) | ||
self.assertIsInstance(mock, MagicMock) | ||
|
@@ -206,23 +191,22 @@ def test_spec_as_normal_positional_AsyncMock(self): | |
def test_spec_async_mock(self): | ||
@patch.object(AsyncClass, 'async_method', spec=True) | ||
def test_async(mock_method): | ||
self.assertTrue(isinstance(mock_method, AsyncMock)) | ||
self.assertIsInstance(mock_method, AsyncMock) | ||
|
||
test_async() | ||
|
||
def test_spec_parent_not_async_attribute_is(self): | ||
@patch(async_foo_name, spec=True) | ||
def test_async(mock_method): | ||
self.assertTrue(isinstance(mock_method, MagicMock)) | ||
self.assertTrue(isinstance(mock_method.async_method, | ||
AsyncMock)) | ||
self.assertIsInstance(mock_method, MagicMock) | ||
self.assertIsInstance(mock_method.async_method, AsyncMock) | ||
|
||
test_async() | ||
|
def test_target_async_spec_not(self): | |
@patch.object(AsyncClass, 'async_method', spec=NormalClass.a) | ||
def test_async_attribute(mock_method): | ||
self.assertTrue(isinstance(mock_method, MagicMock)) | ||
self.assertIsInstance(mock_method, MagicMock) | ||
self.assertFalse(inspect.iscoroutine(mock_method)) | ||
self.assertFalse(inspect.isawaitable(mock_method)) | ||
|
||
|
@@ -231,15 +215,14 @@ def test_async_attribute(mock_method): | |
def test_target_not_async_spec_is(self): | ||
@patch.object(NormalClass, 'a', spec=async_func) | ||
def test_attribute_not_async_spec_is(mock_async_func): | ||
self.assertTrue(isinstance(mock_async_func, AsyncMock)) | ||
self.assertIsInstance(mock_async_func, AsyncMock) | ||
test_attribute_not_async_spec_is() | ||
|
||
def test_spec_async_attributes(self): | ||
@patch(normal_foo_name, spec=AsyncClass) | ||
def test_async_attributes_coroutines(MockNormalClass): | ||
self.assertTrue(isinstance(MockNormalClass.async_method, | ||
AsyncMock)) | ||
self.assertTrue(isinstance(MockNormalClass, MagicMock)) | ||
self.assertIsInstance(MockNormalClass.async_method, AsyncMock) | ||
self.assertIsInstance(MockNormalClass, MagicMock) | ||
|
||
test_async_attributes_coroutines() | ||
|
||
|
@@ -248,30 +231,23 @@ class AsyncSpecSetTest(unittest.TestCase): | |
def test_is_AsyncMock_patch(self): | ||
@patch.object(AsyncClass, 'async_method', spec_set=True) | ||
def test_async(async_method): | ||
self.assertTrue(isinstance(async_method, AsyncMock)) | ||
self.assertIsInstance(async_method, AsyncMock) | ||
|
||
def test_is_async_AsyncMock(self): | ||
mock = AsyncMock(spec_set=AsyncClass.async_method) | ||
self.assertTrue(asyncio.iscoroutinefunction(mock)) | ||
self.assertTrue(isinstance(mock, AsyncMock)) | ||
self.assertIsInstance(mock, AsyncMock) | ||
|
||
def test_is_child_AsyncMock(self): | ||
mock = MagicMock(spec_set=AsyncClass) | ||
self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) | ||
self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) | ||
self.assertTrue(isinstance(mock.async_method, AsyncMock)) | ||
self.assertTrue(isinstance(mock.normal_method, MagicMock)) | ||
self.assertTrue(isinstance(mock, MagicMock)) | ||
self.assertIsInstance(mock.async_method, AsyncMock) | ||
self.assertIsInstance(mock.normal_method, MagicMock) | ||
self.assertIsInstance(mock, MagicMock) | ||
|
||
|
||
class AsyncArguments(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
def test_add_return_value(self): | ||
async def addition(self, var): | ||
return var + 1 | ||
|
@@ -310,13 +286,6 @@ def test_add_side_effect_iterable(self): | |
|
||
|
||
class AsyncContextManagerTest(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
class WithAsyncContextManager: | ||
def __init__(self): | ||
self.entered = False | ||
|
@@ -407,13 +376,6 @@ async def raise_in(context_manager): | |
|
||
|
||
class AsyncIteratorTest(unittest.TestCase): | ||
def setUp(self): | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
class WithAsyncIterator(object): | ||
def __init__(self): | ||
self.items = ["foo", "NormalFoo", "baz"] | ||
|
@@ -476,14 +438,8 @@ async def iterate(iterator): | |
|
||
|
||
class AsyncMockAssert(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.mock = AsyncMock() | ||
self.old_policy = asyncio.events._event_loop_policy | ||
|
||
def tearDown(self): | ||
# Restore the original event loop policy. | ||
asyncio.events._event_loop_policy = self.old_policy | ||
|
||
async def _runnable_test(self, *args): | ||
if not args: | ||
|
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.