diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 300f28c8e2c..3fa3cfdaf21 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -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, @@ -890,6 +890,32 @@ object:: >>> mock() # doctest: +SKIP + + 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 + + >>> a_mock.async_foo + + >>> mock = Mock(ExampleClass) + >>> mock.sync_foo + + >>> mock.async_foo + + + .. method:: assert_awaited() Assert that the mock was awaited at least once. Note that this is separate diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 5d32f88c6b7..488ab1c23de 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -997,8 +997,9 @@ class NonCallableMock(Base): # 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 diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index ed8aefc2a86..7671d55a8cf 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -3,7 +3,7 @@ import inspect 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) @@ -228,33 +228,50 @@ class AsyncAutospecTest(unittest.TestCase): 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) diff --git a/Misc/NEWS.d/next/Library/2019-09-28-20-16-40.bpo-38163.x51-vK.rst b/Misc/NEWS.d/next/Library/2019-09-28-20-16-40.bpo-38163.x51-vK.rst new file mode 100644 index 00000000000..5f7db26e05c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-09-28-20-16-40.bpo-38163.x51-vK.rst @@ -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).