summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLisa Roach <lisaroach14@gmail.com>2019-09-29 01:42:44 (GMT)
committerGitHub <noreply@github.com>2019-09-29 01:42:44 (GMT)
commit9a7d9519506ae807ca48ff02e2ea117ebac3450e (patch)
treeee6ec4e54e88f5bd4038a368084b4ba5524facc0
parentf185a73249189bc3b75b4dc26e9ec2990a587b17 (diff)
downloadcpython-9a7d9519506ae807ca48ff02e2ea117ebac3450e.zip
cpython-9a7d9519506ae807ca48ff02e2ea117ebac3450e.tar.gz
cpython-9a7d9519506ae807ca48ff02e2ea117ebac3450e.tar.bz2
bpo-38108: Makes mock objects inherit from Base (GH-16060)
-rw-r--r--Lib/unittest/mock.py55
-rw-r--r--Lib/unittest/test/testmock/testasync.py55
-rw-r--r--Lib/unittest/test/testmock/testmagicmethods.py3
-rw-r--r--Misc/NEWS.d/next/Library/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst2
4 files changed, 58 insertions, 57 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 0cd7af6..27aa3bc 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -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
diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py
index fde1e4a..624fadc 100644
--- a/Lib/unittest/test/testmock/testasync.py
+++ b/Lib/unittest/test/testmock/testasync.py
@@ -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()
diff --git a/Lib/unittest/test/testmock/testmagicmethods.py b/Lib/unittest/test/testmock/testmagicmethods.py
index 57f85e9..76b3a56 100644
--- a/Lib/unittest/test/testmock/testmagicmethods.py
+++ b/Lib/unittest/test/testmock/testmagicmethods.py
@@ -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
index 0000000..d7eea36
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-09-25-21-37-02.bpo-38108.Jr9HU6.rst
@@ -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.