Skip to content

Commit

Permalink
bpo-38163: Child mocks detect their type as sync or async (pythonGH-1…
Browse files Browse the repository at this point in the history
  • Loading branch information
lisroach committed Sep 30, 2019
1 parent 5bcc6d8 commit 3667e1e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 28 deletions.
28 changes: 27 additions & 1 deletion Doc/library/unittest.mock.rst
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
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
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
@@ -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).

0 comments on commit 3667e1e

Please sign in to comment.