Skip to content

Commit b76ab35

Browse files
miss-islingtonlisroach
authored andcommitted
bpo-38108: Makes mock objects inherit from Base (GH-16060) (GH-16470)
1 parent c9ed9e6 commit b76ab35

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
@@ -415,7 +415,7 @@ def __new__(cls, /, *args, **kw):
415415
if _is_async_obj(bound_args[spec_arg[0]]):
416416
bases = (AsyncMockMixin, cls,)
417417
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
418-
instance = object.__new__(new)
418+
instance = _safe_super(NonCallableMock, cls).__new__(new)
419419
return instance
420420

421421

@@ -995,17 +995,18 @@ def _get_child_mock(self, /, **kw):
995995

996996
_type = type(self)
997997
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
998+
# Any asynchronous magic becomes an AsyncMock
998999
klass = AsyncMock
999-
elif _new_name in _sync_async_magics:
1000-
# Special case these ones b/c users will assume they are async,
1001-
# but they are actually sync (ie. __aiter__)
1002-
klass = MagicMock
10031000
elif issubclass(_type, AsyncMockMixin):
1004-
klass = AsyncMock
1001+
if _new_name in _all_sync_magics:
1002+
# Any synchronous magic becomes a MagicMock
1003+
klass = MagicMock
1004+
else:
1005+
klass = AsyncMock
10051006
elif not issubclass(_type, CallableMixin):
10061007
if issubclass(_type, NonCallableMagicMock):
10071008
klass = MagicMock
1008-
elif issubclass(_type, NonCallableMock) :
1009+
elif issubclass(_type, NonCallableMock):
10091010
klass = Mock
10101011
else:
10111012
klass = _type.__mro__[1]
@@ -1872,6 +1873,7 @@ def _patch_stopall():
18721873
"round trunc floor ceil "
18731874
"bool next "
18741875
"fspath "
1876+
"aiter "
18751877
)
18761878

18771879
numerics = (
@@ -2010,21 +2012,22 @@ def _set_return_value(mock, method, name):
20102012

20112013

20122014

2013-
class MagicMixin(object):
2015+
class MagicMixin(Base):
20142016
def __init__(self, /, *args, **kw):
20152017
self._mock_set_magics() # make magic work for kwargs in init
20162018
_safe_super(MagicMixin, self).__init__(*args, **kw)
20172019
self._mock_set_magics() # fix magic broken by upper level init
20182020

20192021

20202022
def _mock_set_magics(self):
2021-
these_magics = _magics
2023+
orig_magics = _magics | _async_method_magics
2024+
these_magics = orig_magics
20222025

20232026
if getattr(self, "_mock_methods", None) is not None:
2024-
these_magics = _magics.intersection(self._mock_methods)
2027+
these_magics = orig_magics.intersection(self._mock_methods)
20252028

20262029
remove_magics = set()
2027-
remove_magics = _magics - these_magics
2030+
remove_magics = orig_magics - these_magics
20282031

20292032
for entry in remove_magics:
20302033
if entry in type(self).__dict__:
@@ -2052,33 +2055,13 @@ def mock_add_spec(self, spec, spec_set=False):
20522055
self._mock_set_magics()
20532056

20542057

2055-
class AsyncMagicMixin:
2058+
class AsyncMagicMixin(MagicMixin):
20562059
def __init__(self, /, *args, **kw):
2057-
self._mock_set_async_magics() # make magic work for kwargs in init
2060+
self._mock_set_magics() # make magic work for kwargs in init
20582061
_safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
2059-
self._mock_set_async_magics() # fix magic broken by upper level init
2060-
2061-
def _mock_set_async_magics(self):
2062-
these_magics = _async_magics
2063-
2064-
if getattr(self, "_mock_methods", None) is not None:
2065-
these_magics = _async_magics.intersection(self._mock_methods)
2066-
remove_magics = _async_magics - these_magics
2067-
2068-
for entry in remove_magics:
2069-
if entry in type(self).__dict__:
2070-
# remove unneeded magic methods
2071-
delattr(self, entry)
2072-
2073-
# don't overwrite existing attributes if called a second time
2074-
these_magics = these_magics - set(type(self).__dict__)
2075-
2076-
_type = type(self)
2077-
for entry in these_magics:
2078-
setattr(_type, entry, MagicProxy(entry, self))
2079-
2062+
self._mock_set_magics() # fix magic broken by upper level init
20802063

2081-
class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
2064+
class MagicMock(MagicMixin, Mock):
20822065
"""
20832066
MagicMock is a subclass of Mock with default implementations
20842067
of most of the magic methods. You can use MagicMock without having to
@@ -2100,7 +2083,7 @@ def mock_add_spec(self, spec, spec_set=False):
21002083

21012084

21022085

2103-
class MagicProxy(object):
2086+
class MagicProxy(Base):
21042087
def __init__(self, name, parent):
21052088
self.name = name
21062089
self.parent = parent

Lib/unittest/test/testmock/testasync.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,43 @@ def test_add_side_effect_iterable(self):
375375
RuntimeError('coroutine raised StopIteration')
376376
)
377377

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

379416
class AsyncContextManagerTest(unittest.TestCase):
380417
class WithAsyncContextManager:
@@ -402,24 +439,6 @@ async def main(self):
402439
val = await response.json()
403440
return val
404441

405-
def test_async_magic_methods_are_async_mocks_with_magicmock(self):
406-
cm_mock = MagicMock(self.WithAsyncContextManager())
407-
self.assertIsInstance(cm_mock.__aenter__, AsyncMock)
408-
self.assertIsInstance(cm_mock.__aexit__, AsyncMock)
409-
410-
def test_magicmock_has_async_magic_methods(self):
411-
cm = MagicMock(name='magic_cm')
412-
self.assertTrue(hasattr(cm, "__aenter__"))
413-
self.assertTrue(hasattr(cm, "__aexit__"))
414-
415-
def test_magic_methods_are_async_functions(self):
416-
cm = MagicMock(name='magic_cm')
417-
self.assertIsInstance(cm.__aenter__, AsyncMock)
418-
self.assertIsInstance(cm.__aexit__, AsyncMock)
419-
# AsyncMocks are also coroutine functions
420-
self.assertTrue(asyncio.iscoroutinefunction(cm.__aenter__))
421-
self.assertTrue(asyncio.iscoroutinefunction(cm.__aexit__))
422-
423442
def test_set_return_value_of_aenter(self):
424443
def inner_test(mock_type):
425444
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)