]> granicus.if.org Git - python/commitdiff
[3.8] bpo-38163: Child mocks detect their type as sync or async (GH-16471) (GH-16484)
authorLisa Roach <lisaroach14@gmail.com>
Mon, 30 Sep 2019 05:22:44 +0000 (22:22 -0700)
committerGitHub <noreply@github.com>
Mon, 30 Sep 2019 05:22:44 +0000 (22:22 -0700)
Doc/library/unittest.mock.rst
Lib/unittest/mock.py
Lib/unittest/test/testmock/testasync.py
Misc/NEWS.d/next/Library/2019-09-28-20-16-40.bpo-38163.x51-vK.rst [new file with mode: 0644]

index 300f28c8e2cec785976ab575ea5688778c6224d4..3fa3cfdaf21a35bc6c9c435b558c1f93e4478ba2 100644 (file)
@@ -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
     <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
index 5d32f88c6b7872e92fb63db81b5be5e59da9068d..488ab1c23de423b3fb5315038f504f4f337e0942 100644 (file)
@@ -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
index ed8aefc2a86692a3d78608854d3c9bd628179912..7671d55a8cfba8cac3ce11b73fed52f1f0307f17 100644 (file)
@@ -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 (file)
index 0000000..5f7db26
--- /dev/null
@@ -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).