bpo-38108: Makes mock objects inherit from Base (GH-16060)
This commit is contained in:
parent
f185a73249
commit
9a7d951950
|
@ -409,7 +409,7 @@ class NonCallableMock(Base):
|
||||||
if spec_arg and _is_async_obj(spec_arg):
|
if spec_arg and _is_async_obj(spec_arg):
|
||||||
bases = (AsyncMockMixin, cls)
|
bases = (AsyncMockMixin, cls)
|
||||||
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
|
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
|
||||||
instance = object.__new__(new)
|
instance = _safe_super(NonCallableMock, cls).__new__(new)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
@ -990,17 +990,18 @@ class NonCallableMock(Base):
|
||||||
|
|
||||||
_type = type(self)
|
_type = type(self)
|
||||||
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
|
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
|
||||||
|
# Any asynchronous magic becomes an AsyncMock
|
||||||
klass = 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):
|
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):
|
elif not issubclass(_type, CallableMixin):
|
||||||
if issubclass(_type, NonCallableMagicMock):
|
if issubclass(_type, NonCallableMagicMock):
|
||||||
klass = MagicMock
|
klass = MagicMock
|
||||||
elif issubclass(_type, NonCallableMock) :
|
elif issubclass(_type, NonCallableMock):
|
||||||
klass = Mock
|
klass = Mock
|
||||||
else:
|
else:
|
||||||
klass = _type.__mro__[1]
|
klass = _type.__mro__[1]
|
||||||
|
@ -1886,6 +1887,7 @@ magic_methods = (
|
||||||
"round trunc floor ceil "
|
"round trunc floor ceil "
|
||||||
"bool next "
|
"bool next "
|
||||||
"fspath "
|
"fspath "
|
||||||
|
"aiter "
|
||||||
)
|
)
|
||||||
|
|
||||||
numerics = (
|
numerics = (
|
||||||
|
@ -2024,7 +2026,7 @@ def _set_return_value(mock, method, name):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MagicMixin(object):
|
class MagicMixin(Base):
|
||||||
def __init__(self, /, *args, **kw):
|
def __init__(self, /, *args, **kw):
|
||||||
self._mock_set_magics() # make magic work for kwargs in init
|
self._mock_set_magics() # make magic work for kwargs in init
|
||||||
_safe_super(MagicMixin, self).__init__(*args, **kw)
|
_safe_super(MagicMixin, self).__init__(*args, **kw)
|
||||||
|
@ -2032,13 +2034,14 @@ class MagicMixin(object):
|
||||||
|
|
||||||
|
|
||||||
def _mock_set_magics(self):
|
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:
|
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 = set()
|
||||||
remove_magics = _magics - these_magics
|
remove_magics = orig_magics - these_magics
|
||||||
|
|
||||||
for entry in remove_magics:
|
for entry in remove_magics:
|
||||||
if entry in type(self).__dict__:
|
if entry in type(self).__dict__:
|
||||||
|
@ -2066,33 +2069,13 @@ class NonCallableMagicMock(MagicMixin, NonCallableMock):
|
||||||
self._mock_set_magics()
|
self._mock_set_magics()
|
||||||
|
|
||||||
|
|
||||||
class AsyncMagicMixin:
|
class AsyncMagicMixin(MagicMixin):
|
||||||
def __init__(self, /, *args, **kw):
|
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)
|
_safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
|
||||||
self._mock_set_async_magics() # fix magic broken by upper level init
|
self._mock_set_magics() # fix magic broken by upper level init
|
||||||
|
|
||||||
def _mock_set_async_magics(self):
|
class MagicMock(MagicMixin, Mock):
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
|
|
||||||
"""
|
"""
|
||||||
MagicMock is a subclass of Mock with default implementations
|
MagicMock is a subclass of Mock with default implementations
|
||||||
of most of the magic methods. You can use MagicMock without having to
|
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):
|
def __init__(self, name, parent):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
|
@ -379,6 +379,43 @@ class AsyncArguments(unittest.TestCase):
|
||||||
RuntimeError('coroutine raised StopIteration')
|
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):
|
class AsyncContextManagerTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -406,24 +443,6 @@ class AsyncContextManagerTest(unittest.TestCase):
|
||||||
val = await response.json()
|
val = await response.json()
|
||||||
return val
|
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 test_set_return_value_of_aenter(self):
|
||||||
def inner_test(mock_type):
|
def inner_test(mock_type):
|
||||||
pc = self.ProductionCode()
|
pc = self.ProductionCode()
|
||||||
|
|
|
@ -271,9 +271,6 @@ class TestMockingMagicMethods(unittest.TestCase):
|
||||||
self.assertEqual(mock == mock, True)
|
self.assertEqual(mock == mock, True)
|
||||||
self.assertEqual(mock != mock, False)
|
self.assertEqual(mock != mock, False)
|
||||||
|
|
||||||
|
|
||||||
# This should be fixed with issue38163
|
|
||||||
@unittest.expectedFailure
|
|
||||||
def test_asyncmock_defaults(self):
|
def test_asyncmock_defaults(self):
|
||||||
mock = AsyncMock()
|
mock = AsyncMock()
|
||||||
self.assertEqual(int(mock), 1)
|
self.assertEqual(int(mock), 1)
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue