8000 bpo-38108: Makes mock objects inherit from Base (GH-16060) · python/cpython@9a7d951 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9a7d951

Browse files
authored
bpo-38108: Makes mock objects inherit from Base (GH-16060)
1 parent f185a73 commit 9a7d951

File tree

4 files changed

+58
-57
lines changed

4 files changed

+58
-57
lines changed

Lib/unittest/mock.py

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def __new__(cls, /, *args, **kw):
409409
if spec_arg and _is_async_obj(spec_arg):
410410
bases = (AsyncMockMixin, cls)
411411
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
412-
instance = object.__new__(new)
412+
instance = _safe_super(NonCallableMock, cls).__new__(new)
413413
return instance
414414

415415

@@ -990,17 +990,18 @@ def _get_child_mock(self, /, **kw):
990990

991991
_type = type(self)
992992
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
993+
# Any asynchronous magic becomes an AsyncMock
993994
klass = AsyncMock
994-
elif _new_name in _sync_async_magics:
995-
# Special case these ones b/c users will assume they are async,
996-
# but they are actually sync (ie. __aiter__)
997-
klass = MagicMock
998995
elif issubclass(_type, AsyncMockMixin):
999-
klass = AsyncMock
996+
if _new_name in _all_sync_magics:
997+
# Any synchronous magic becomes a MagicMock
998+
klass = MagicMock
999+
else:
1000+
klass = AsyncMock
10001001
elif not issubclass(_type, CallableMixin):
10011002
if issubclass(_type, NonCallableMagicMock):
10021003
klass = MagicMock
1003-
elif issubclass(_type, NonCallableMock) :
1004+
elif issubclass(_type, NonCallableMock):
10041005
klass = Mock
10051006
else:
10061007
klass = _type.__mro__[1]
@@ -1886,6 +1887,7 @@ def _patch_stopall():
18861887
"round trunc floor ceil "
18871888
"bool next "
18881889
"fspath "
1890+
"aiter "
18891891
)
18901892

18911893
numerics = (
@@ -2024,21 +2026,22 @@ def _set_return_value(mock, method, name):
20242026

20252027

20262028

2027-
class MagicMixin(object):
2029+
class MagicMixin(Base):
20282030
def __init__(self, /, *args, **kw):
20292031
self._mock_set_magics() # make magic work for kwargs in init
20302032
_safe_super(MagicMixin, self).__init__(*args, **kw)
20312033
self._mock_set_magics() # fix magic broken by upper level init
20322034

20332035

20342036
def _mock_set_magics(self):
2035-
these_magics = _magics
2037+
orig_magics = _magics | _async_method_magics
2038+
these_magics = orig_magics
20362039

20372040
if getattr(self, "_mock_methods", None) is not None:
2038-
these_magics = _magics.intersection(self._mock_methods)
2041+
these_magics = orig_magics.intersection(self._mock_methods)
20392042

20402043
remove_magics = set()
2041-
remove_magics = _magics - these_magics
2044+
remove_magics = orig_magics - these_magics
20422045

20432046
for entry in remove_magics:
20442047
if entry in type(self).__dict__:
@@ -2066,33 +2069,13 @@ def mock_add_spec(self, spec, spec_set=False):
20662069
self._mock_set_magics()
20672070

20682071

2069-
class AsyncMagicMixin:
2072+
class AsyncMagicMixin(MagicMixin):
20702073
def __init__(self, /, *args, **kw):
2071-
self._mock_set_async_magics() # make magic work for kwargs in init
2074+
self._mock_set_magics() # make magic work for kwargs in init
20722075
_safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
2073-
self._mock_set_async_magics() # fix magic broken by upper level init
2074-
2075-
def _mock_set_async_magics(self):
2076-
these_magics = _async_magics
2077-
2078-
if getattr(self, "_mock_methods", None) is not None:
2079-
these_magics = _async_magics.intersection(self._mock_methods)
2080-
remove_magics = _async_magics - these_magics
2081-
2082-
for entry in remove_magics:
2083-
if entry in type(self).__dict__:
2084-
# remove unneeded magic methods
2085-
delattr(self, entry)
2086-
2087-
# don't overwrite existing attributes if called a second time
2088-
these_magics = these_magics - set(type(self).__dict__)
2089-
2090-
_type = type(self)
2091-
for entry in these_magics:
2092-
setattr(_type, entry, MagicProxy(entry, self))
2093-
2076+
self._mock_set_magics() # fix magic broken by upper level init
20942077

2095-
class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
2078+
class MagicMock(MagicMixin, Mock):
20962079
"""
20972080
MagicMock is a subclass of Mock with default implementations
20982081
of most of the magic methods. You can use MagicMock without having to
@@ -2114,7 +2097,7 @@ def mock_add_spec(self, spec, spec_set=False):
21142097

21152098

21162099

2117-
class MagicProxy(object):
2100+
class MagicProxy(Base):
21182101
def __init__(self, name, parent):
21192102
self.name = name
21202103
self.parent = parent

Lib/unittest/test/testmock/testasync.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,43 @@ def test_add_side_effect_iterable(self):
379379
RuntimeError('coroutine raised StopIteration')
380380
)
381381

382+
class AsyncMagicMethods(unittest.TestCase):
383+
def test_async_magic_methods_return_async_mocks(self):
384+
m_mock = MagicMock()
385+
self.assertIsInstance(m_mock.__aenter__, AsyncMock)
386+
self.assertIsInstance(m_mock.__aexit__, AsyncMock)
387+
self.assertIsInstance(m_mock.__anext__, AsyncMock)
388+
# __aiter__ is actually a synchronous object
389+
# so should return a MagicMock
390+
self.assertIsInstance(m_mock.__aiter__, MagicMock)
391+
392+
def test_sync_magic_methods_return_magic_mocks(self):
393+
a_mock = AsyncMock()
394+
self.assertIsInstance(a_mock.__enter__, MagicMock)
395+
self.assertIsInstance(a_mock.__exit__, MagicMock)
396+
self.assertIsInstance(a_mock.__next__, MagicMock)
397+
self.assertIsInstance(a_mock.__len__, MagicMock)
398+
399+
def test_magicmock_has_async_magic_methods(self):
400+
m_mock = MagicMock()
401+
self.assertTrue(hasattr(m_mock, "__aenter__"))
402+
self.assertTrue(hasattr(m_mock, "__aexit__"))
403+
self.assertTrue(hasattr(m_mock, "__anext__"))
404+
405+
def test_asyncmock_has_sync_magic_methods(self):
406+
a_mock = AsyncMock()
407+
self.assertTrue(hasattr(a_mock, "__enter__"))
408+
self.assertTrue(hasattr(a_mock, "__exit__"))
409+
self.assertTrue(hasattr(a_mock, "__next__"))
410+
self.assertTrue(hasattr(a_mock, "__len__"))
411+
412+
def test_magic_methods_are_async_functions(self):
413+
m_mock = MagicMock()
414+
self.assertIsInstance(m_mock.__aenter__, AsyncMock)
415+
self.assertIsInstance(m_mock.__aexit__, AsyncMock)
416+
# AsyncMocks are also coroutine functions
417+
self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aenter__))
418+
self.assertTrue(asyncio.iscoroutinefunction(m_mock.__aexit__))
382419

383420
class AsyncContextManagerTest(unittest.TestCase):
384421

@@ -406,24 +443,6 @@ async def main(self):
406443
val = await response.json()
407444
return val
408445

409-
def test_async_magic_methods_are_async_mocks_with_magicmock(self):
410-
cm_mock = MagicMock(self.WithAsyncContextManager())
411-
self.assertIsInstance(cm_mock.__aenter__, AsyncMock)
412-
self.assertIsInstance(cm_mock.__aexit__, AsyncMock)
413-
414-
def test_magicmock_has_async_magic_methods(self):
415-
cm = MagicMock(name='magic_cm')
416-
self.assertTrue(hasattr(cm, "__aenter__"))
417-
self.assertTrue(hasattr(cm, "__aexit__"))
418-
419-
def test_magic_methods_are_async_functions(self):
420-
cm = MagicMock(name='magic_cm')
421-
self.assertIsInstance(cm.__aenter__, AsyncMock)
422-
self.assertIsInstance(cm.__aexit__, AsyncMock)
423-
# AsyncMocks are also coroutine functions
424-
self.assertTrue(asyncio.iscoroutinefunction(cm.__aenter__))
425-
self.assertTrue(asyncio.iscoroutinefunction(cm.__aexit__))
426-
427446
def test_set_return_value_of_aenter(self):
428447
def inner_test(mock_type):
429448
pc = self.ProductionCode()

Lib/unittest/test/testmock/testmagicmethods.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,6 @@ def test_magic_mock_equality(self):
271271
self.assertEqual(mock == mock, True)
272272
self.assertEqual(mock != mock, False)
273273

274-
275-
# This should be fixed with issue38163
276-
@unittest.expectedFailure
277274
def test_asyncmock_defaults(self):
278275
mock = AsyncMock()
279276
self.assertEqual(int(mock), 1)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Any synchronous magic methods on an AsyncMock now return a MagicMock. Any
2+
asynchronous magic methods on a MagicMock now return an AsyncMock.

0 commit comments

Comments
 (0)
0