Skip to content
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-38163: Child mocks detect their type as sync or async #16471

Merged
merged 6 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion Doc/library/unittest.mock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,7 @@ object::
True

The result of ``mock()`` is an async function which will have the outcome
of ``side_effect`` or ``return_value``:
of ``side_effect`` or ``return_value`` after it has been awaited:

- if ``side_effect`` is a function, the async function will return the
result of that function,
Expand All @@ -890,6 +890,32 @@ object::
>>> mock() # doctest: +SKIP
<coroutine object AsyncMockMixin._mock_call at ...>


Setting the *spec* of a :class:`Mock`, :class:`MagicMock`, or :class:`AsyncMock`
to a class with asynchronous and synchronous functions will automatically
detect the synchronous functions and set them as :class:`MagicMock` (if the
parent mock is :class:`AsyncMock` or :class:`MagicMock`) or :class:`Mock` (if
the parent mock is :class:`Mock`). All asynchronous functions will be
:class:`AsyncMock`.

>>> class ExampleClass:
... def sync_foo():
... pass
... async def async_foo():
... pass
...
>>> a_mock = AsyncMock(ExampleClass)
>>> a_mock.sync_foo
<MagicMock name='mock.sync_foo' id='...'>
>>> a_mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>
>>> mock = Mock(ExampleClass)
>>> mock.sync_foo
<Mock name='mock.sync_foo' id='...'>
>>> mock.async_foo
<AsyncMock name='mock.async_foo' id='...'>


.. method:: assert_awaited()

Assert that the mock was awaited at least once. Note that this is separate
Expand Down
5 changes: 3 additions & 2 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,8 +992,9 @@ def _get_child_mock(self, /, **kw):
# Any asynchronous magic becomes an AsyncMock
klass = AsyncMock
elif issubclass(_type, AsyncMockMixin):
if _new_name in _all_sync_magics:
# Any synchronous magic becomes a MagicMock
if (_new_name in _all_sync_magics or
self._mock_methods and _new_name in self._mock_methods):
# Any synchronous method on AsyncMock becomes a MagicMock
klass = MagicMock
else:
klass = AsyncMock
Expand Down
67 changes: 42 additions & 25 deletions Lib/unittest/test/testmock/testasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
import unittest

from unittest.mock import (ANY, call, AsyncMock, patch, MagicMock,
from unittest.mock import (ANY, call, AsyncMock, patch, MagicMock, Mock,
create_autospec, sentinel, _CallList)


Expand Down Expand Up @@ -232,33 +232,50 @@ async def test_async():


class AsyncSpecTest(unittest.TestCase):
def test_spec_as_async_positional_magicmock(self):
mock = MagicMock(async_func)
self.assertIsInstance(mock, MagicMock)
m = mock()
self.assertTrue(inspect.isawaitable(m))
asyncio.run(m)
def test_spec_normal_methods_on_class(self):
def inner_test(mock_type):
mock = mock_type(AsyncClass)
self.assertIsInstance(mock.async_method, AsyncMock)
self.assertIsInstance(mock.normal_method, MagicMock)

def test_spec_as_async_kw_magicmock(self):
mock = MagicMock(spec=async_func)
self.assertIsInstance(mock, MagicMock)
m = mock()
self.assertTrue(inspect.isawaitable(m))
asyncio.run(m)
for mock_type in [AsyncMock, MagicMock]:
with self.subTest(f"test method types with {mock_type}"):
inner_test(mock_type)

def test_spec_as_async_kw_AsyncMock(self):
mock = AsyncMock(spec=async_func)
self.assertIsInstance(mock, AsyncMock)
m = mock()
self.assertTrue(inspect.isawaitable(m))
asyncio.run(m)
def test_spec_normal_methods_on_class_with_mock(self):
mock = Mock(AsyncClass)
self.assertIsInstance(mock.async_method, AsyncMock)
self.assertIsInstance(mock.normal_method, Mock)

def test_spec_as_async_positional_AsyncMock(self):
mock = AsyncMock(async_func)
self.assertIsInstance(mock, AsyncMock)
m = mock()
self.assertTrue(inspect.isawaitable(m))
asyncio.run(m)
def test_spec_mock_type_kw(self):
def inner_test(mock_type):
async_mock = mock_type(spec=async_func)
self.assertIsInstance(async_mock, mock_type)
with self.assertWarns(RuntimeWarning):
# Will raise a warning because never awaited
self.assertTrue(inspect.isawaitable(async_mock()))

sync_mock = mock_type(spec=normal_func)
self.assertIsInstance(sync_mock, mock_type)

for mock_type in [AsyncMock, MagicMock, Mock]:
with self.subTest(f"test spec kwarg with {mock_type}"):
inner_test(mock_type)

def test_spec_mock_type_positional(self):
def inner_test(mock_type):
async_mock = mock_type(async_func)
self.assertIsInstance(async_mock, mock_type)
with self.assertWarns(RuntimeWarning):
# Will raise a warning because never awaited
self.assertTrue(inspect.isawaitable(async_mock()))

sync_mock = mock_type(normal_func)
self.assertIsInstance(sync_mock, mock_type)

for mock_type in [AsyncMock, MagicMock, Mock]:
with self.subTest(f"test spec positional with {mock_type}"):
inner_test(mock_type)

def test_spec_as_normal_kw_AsyncMock(self):
mock = AsyncMock(spec=normal_func)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Child mocks will now detect their type as either synchronous or
asynchronous, asynchronous child mocks will be AsyncMocks and synchronous
child mocks will be either MagicMock or Mock (depending on their parent
type).