]> granicus.if.org Git - python/commitdiff
bpo-38108: Makes mock objects inherit from Base (GH-16060)
authorLisa Roach <lisaroach14@gmail.com>
Sun, 29 Sep 2019 01:42:44 +0000 (18:42 -0700)
committerGitHub <noreply@github.com>
Sun, 29 Sep 2019 01:42:44 +0000 (18:42 -0700)
Lib/unittest/mock.py
Lib/unittest/test/testmock/testasync.py
Lib/unittest/test/testmock/testmagicmethods.py
Misc/NEWS.d/next/Library/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst [new file with mode: 0644]

index 0cd7af65b94020e0b045c02bbc15d584c5706454..27aa3bc65c9a73bfe36159976fa54c353eb7070c 100644 (file)
@@ -409,7 +409,7 @@ class NonCallableMock(Base):
             if spec_arg and _is_async_obj(spec_arg):
                 bases = (AsyncMockMixin, cls)
         new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
-        instance = object.__new__(new)
+        instance = _safe_super(NonCallableMock, cls).__new__(new)
         return instance
 
 
@@ -990,17 +990,18 @@ class NonCallableMock(Base):
 
         _type = type(self)
         if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
+            # Any asynchronous magic becomes an AsyncMock
             klass = AsyncMock
-        elif _new_name  in _sync_async_magics:
-            # Special case these ones b/c users will assume they are async,
-            # but they are actually sync (ie. __aiter__)
-            klass = MagicMock
         elif issubclass(_type, AsyncMockMixin):
-            klass = AsyncMock
+            if _new_name in _all_sync_magics:
+                # Any synchronous magic becomes a MagicMock
+                klass = MagicMock
+            else:
+                klass = AsyncMock
         elif not issubclass(_type, CallableMixin):
             if issubclass(_type, NonCallableMagicMock):
                 klass = MagicMock
-            elif issubclass(_type, NonCallableMock) :
+            elif issubclass(_type, NonCallableMock):
                 klass = Mock
         else:
             klass = _type.__mro__[1]
@@ -1886,6 +1887,7 @@ magic_methods = (
     "round trunc floor ceil "
     "bool next "
     "fspath "
+    "aiter "
 )
 
 numerics = (
@@ -2024,7 +2026,7 @@ def _set_return_value(mock, method, name):
 
 
 
-class MagicMixin(object):
+class MagicMixin(Base):
     def __init__(self, /, *args, **kw):
         self._mock_set_magics()  # make magic work for kwargs in init
         _safe_super(MagicMixin, self).__init__(*args, **kw)
@@ -2032,13 +2034,14 @@ class MagicMixin(object):
 
 
     def _mock_set_magics(self):
-        these_magics = _magics
+        orig_magics = _magics | _async_method_magics
+        these_magics = orig_magics
 
         if getattr(self, "_mock_methods", None) is not None:
-            these_magics = _magics.intersection(self._mock_methods)
+            these_magics = orig_magics.intersection(self._mock_methods)
 
             remove_magics = set()
-            remove_magics = _magics - these_magics
+            remove_magics = orig_magics - these_magics
 
             for entry in remove_magics:
                 if entry in type(self).__dict__:
@@ -2066,33 +2069,13 @@ class NonCallableMagicMock(MagicMixin, NonCallableMock):
         self._mock_set_magics()
 
 
-class AsyncMagicMixin:
+class AsyncMagicMixin(MagicMixin):
     def __init__(self, /, *args, **kw):
-        self._mock_set_async_magics()  # make magic work for kwargs in init
+        self._mock_set_magics()  # make magic work for kwargs in init
         _safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
-        self._mock_set_async_magics()  # fix magic broken by upper level init
-
-    def _mock_set_async_magics(self):
-        these_magics = _async_magics
-
-        if getattr(self, "_mock_methods", None) is not None:
-            these_magics = _async_magics.intersection(self._mock_methods)
-            remove_magics = _async_magics - these_magics
-
-            for entry in remove_magics:
-                if entry in type(self).__dict__:
-                    # remove unneeded magic methods
-                    delattr(self, entry)
-
-        # don't overwrite existing attributes if called a second time
-        these_magics = these_magics - set(type(self).__dict__)
-
-        _type = type(self)
-        for entry in these_magics:
-            setattr(_type, entry, MagicProxy(entry, self))
-
+        self._mock_set_magics()  # fix magic broken by upper level init
 
-class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
+class MagicMock(MagicMixin, Mock):
     """
     MagicMock is a subclass of Mock with default implementations
     of most of the magic methods. You can use MagicMock without having to
@@ -2114,7 +2097,7 @@ class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
 
 
 
-class MagicProxy(object):
+class MagicProxy(Base):
     def __init__(self, name, parent):
         self.name = name
         self.parent = parent
index fde1e4a690b2709101cb863ef21f7c29ac295a67..624fadca264950f39b9069483d2b184dd6fa0822 100644 (file)
@@ -379,6 +379,43 @@ class AsyncArguments(unittest.TestCase):
                 RuntimeError('coroutine raised StopIteration')
             )
 
+class AsyncMagicMethods(unittest.TestCase):
+    def test_async_magic_methods_return_async_mocks(self):
+        m_mock = MagicMock()
+        self.assertIsInstance(m_mock.__aenter__, AsyncMock)
+        self.assertIsInstance(m_mock.__aexit__, AsyncMock)
+        self.assertIsInstance(m_mock.__anext__, AsyncMock)
+        # __aiter__ is actually a synchronous object
+        # so should return a MagicMock
+        self.assertIsInstance(m_mock.__aiter__, MagicMock)
+
+    def test_sync_magic_methods_return_magic_mocks(self):
+        a_mock = AsyncMock()
+        self.assertIsInstance(a_mock.__enter__, MagicMock)
+        self.assertIsInstance(a_mock.__exit__, MagicMock)
+        self.assertIsInstance(a_mock.__next__, MagicMock)
+        self.assertIsInstance(a_mock.__len__, MagicMock)
+
+    def test_magicmock_has_async_magic_methods(self):
+        m_mock = MagicMock()
+        self.assertTrue(hasattr(m_mock, "__aenter__"))
+        self.assertTrue(hasattr(m_mock, "__aexit__"))
+        self.assertTrue(hasattr(m_mock, "__anext__"))
+
+    def test_asyncmock_has_sync_magic_methods(self):
+        a_mock = AsyncMock()
+        self.assertTrue(hasattr(a_mock, "__enter__"))
+        self.assertTrue(hasattr(a_mock, "__exit__"))
+        self.assertTrue(hasattr(a_mock, "__next__"))
+        self.assertTrue(hasattr(a_mock, "__len__"))
+
+    def test_magic_methods_are_async_functions(self):
+        m_mock = MagicMock()
+        self.assertIsInstance(m_mock.__aenter__, AsyncMock)
+        self.assertIsInstance(m_mock.__aexit__, AsyncMock)
+        # AsyncMocks are also coroutine functions
+        self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aenter__))
+        self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aexit__))
 
 class AsyncContextManagerTest(unittest.TestCase):
 
@@ -406,24 +443,6 @@ class AsyncContextManagerTest(unittest.TestCase):
                 val = await response.json()
                 return val
 
-    def test_async_magic_methods_are_async_mocks_with_magicmock(self):
-        cm_mock = MagicMock(self.WithAsyncContextManager())
-        self.assertIsInstance(cm_mock.__aenter__, AsyncMock)
-        self.assertIsInstance(cm_mock.__aexit__, AsyncMock)
-
-    def test_magicmock_has_async_magic_methods(self):
-        cm = MagicMock(name='magic_cm')
-        self.assertTrue(hasattr(cm, "__aenter__"))
-        self.assertTrue(hasattr(cm, "__aexit__"))
-
-    def test_magic_methods_are_async_functions(self):
-        cm = MagicMock(name='magic_cm')
-        self.assertIsInstance(cm.__aenter__, AsyncMock)
-        self.assertIsInstance(cm.__aexit__, AsyncMock)
-        # AsyncMocks are also coroutine functions
-        self.assertTrue(asyncio.iscoroutinefunction(cm.__aenter__))
-        self.assertTrue(asyncio.iscoroutinefunction(cm.__aexit__))
-
     def test_set_return_value_of_aenter(self):
         def inner_test(mock_type):
             pc = self.ProductionCode()
index 57f85e951e20aba6ce3ce2dee4948d6cfefc245a..76b3a560de090ae8aefadd6222605a3ed6791b3a 100644 (file)
@@ -271,9 +271,6 @@ class TestMockingMagicMethods(unittest.TestCase):
         self.assertEqual(mock == mock, True)
         self.assertEqual(mock != mock, False)
 
-
-    # This should be fixed with issue38163
-    @unittest.expectedFailure
     def test_asyncmock_defaults(self):
         mock = AsyncMock()
         self.assertEqual(int(mock), 1)
diff --git a/Misc/NEWS.d/next/Library/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst b/Misc/NEWS.d/next/Library/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst
new file mode 100644 (file)
index 0000000..d7eea36
--- /dev/null
@@ -0,0 +1,2 @@
+Any synchronous magic methods on an AsyncMock now return a MagicMock. Any
+asynchronous magic methods on a MagicMock now return an AsyncMock.