From 4353041ac52e379cdaf4902896995cc83a37c980 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Mon, 14 May 2018 10:08:38 -0400 Subject: [PATCH 01/24] Initial commmit adding asyncio mock support. --- Lib/unittest/mock.py | 374 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 367 insertions(+), 7 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index db1e642c00b7f9..3653e71a5ed8ae 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -13,6 +13,7 @@ 'ANY', 'call', 'create_autospec', + 'CoroutineMock', 'FILTER_DIR', 'NonCallableMock', 'NonCallableMagicMock', @@ -24,7 +25,7 @@ __version__ = '1.0' - +import asyncio import inspect import pprint import sys @@ -250,7 +251,7 @@ def __init__(self): def __getattr__(self, name): if name == '__bases__': - # Without this help(unittest.mock) raises an exception + # Without this help() raises an exception raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) @@ -357,8 +358,43 @@ def __init__(self, *args, **kwargs): pass +_is_coroutine = asyncio.iscoroutine +_isawaitable = inspect.isawaitable + +def _get_is_coroutine(self): + return self.__dict__['_mock_is_coroutine'] + + +def _set_is_coroutine(self, value): + # property setters and getters are overridden by Mock(), we need to + # update the dict to add values + value = _is_coroutine if bool(value) else False + self.__dict__['_mock_is_coroutine'] = value + + +class IsCoroutineMeta(type): + def __new__(meta, name, base, namespace): + namespace.update({ + '_get_is_coroutine': _get_is_coroutine, + '_set_is_coroutine': _set_is_coroutine, + 'is_coroutine': property(_get_is_coroutine, _set_is_coroutine, + doc="True if the object mocked is a coroutine"), + '_is_coroutine': property(_get_is_coroutine), + }) + + def __setattr__(self, name, value): + + if name == 'is_coroutine': + self._set_is_coroutine(value) + else: + return base[0].__setattr__(self, name, value) -class NonCallableMock(Base): + namespace['__setattr__'] = __setattr__ + + return super().__new__(meta, name, base, namespace) + + +class NonCallableMock(Base, metaclass=IsCoroutineMeta): """A non-callable version of `Mock`""" def __new__(cls, *args, **kw): @@ -372,8 +408,9 @@ def __new__(cls, *args, **kw): def __init__( self, spec=None, wraps=None, name=None, spec_set=None, - parent=None, _spec_state=None, _new_name='', _new_parent=None, - _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs + parent=None, is_coroutine=None, _spec_state=None, _new_name='', + _new_parent=None, _spec_as_instance=False, _eat_self=None, + unsafe=False, **kwargs ): if _new_parent is None: _new_parent = parent @@ -414,6 +451,7 @@ def __init__( _spec_state ) + self._set_is_coroutine(is_coroutine) def attach_mock(self, mock, attribute): """ @@ -925,6 +963,98 @@ def _try_iter(obj): return obj +class _AwaitEvent: + def __init__(self, mock): + self._mock = mock + self._condition = None + + @asyncio.coroutine + def wait(self, skip=0): + """ + Wait for await. + + :param skip: How many awaits will be skipped. + As a result, the mock should be awaited at least + ``skip + 1`` times. + """ + def predicate(mock): + return mock.await_count > skip + + return (yield from self.wait_for(predicate)) + + @asyncio.coroutine + def wait_next(self, skip=0): + """ + Wait for the next await. + + Unlike :meth:`wait` that counts any await, mock has to be awaited once + more, disregarding to the current + :attr:`asynctest.CoroutineMock.await_count`. + + :param skip: How many awaits will be skipped. + As a result, the mock should be awaited at least + ``skip + 1`` more times. + """ + await_count = self._mock.await_count + + def predicate(mock): + return mock.await_count > await_count + skip + + return (yield from self.wait_for(predicate)) + + @asyncio.coroutine + def wait_for(self, predicate): + """ + Wait for a given predicate to become True. + + :param predicate: A callable that receives mock which result + will be interpreted as a boolean value. + The final predicate value is the return value. + """ + condition = self._get_condition() + + try: + yield from condition.acquire() + + def _predicate(): + return predicate(self._mock) + + return (yield from condition.wait_for(_predicate)) + finally: + condition.release() + + @asyncio.coroutine + def _notify(self): + condition = self._get_condition() + + try: + yield from condition.acquire() + condition.notify_all() + finally: + condition.release() + + def _get_condition(self): + """ + Creation of condition is delayed, to minimize the change of using the + wrong loop. + + A user may create a mock with _AwaitEvent before selecting the + execution loop. Requiring a user to delay creation is error-prone and + inflexible. Instead, condition is created when user actually starts to + use the mock. + """ + # No synchronization is needed: + # - asyncio is thread unsafe + # - there are no awaits here, method will be executed without + # switching asyncio context. + if self._condition is None: + self._condition = asyncio.Condition() + + return self._condition + + def __bool__(self): + return self._mock.await_count != 0 + class CallableMixin(Base): @@ -1026,7 +1156,6 @@ def _mock_call(_mock_self, *args, **kwargs): return ret_val - class Mock(CallableMixin, NonCallableMock): """ Create a new `Mock` object. `Mock` takes several optional arguments @@ -1079,6 +1208,231 @@ class or instance) that acts as the specification for the mock object. If """ +class CoroutineMock(Mock): + """ + Enhance :class:`~asynctest.mock.Mock` with features allowing to mock + a coroutine function. + + The :class:`~asynctest.CoroutineMock` object will behave so the object is + recognized as coroutine function, and the result of a call as a coroutine: + + >>> mock = CoroutineMock() + >>> asyncio.iscoroutinefunction(mock) + True + >>> asyncio.iscoroutine(mock()) + True + + + The result of ``mock()`` is a coroutine which will have the outcome of + ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the coroutine will return the result + of that function, + - if ``side_effect`` is an exception, the coroutine will raise the + exception, + - if ``side_effect`` is an iterable, the coroutine will return the next + value of the iterable, however, if the sequence of result is exhausted, + ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the coroutine will return the value + defined by ``return_value``, hence, by default, the coroutine returns + a new :class:`~asynctest.CoroutineMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is a coroutine, the + mock coroutine obtained when the mock object is called will be this + coroutine itself (and not a coroutine returning a coroutine). + + The test author can also specify a wrapped object with ``wraps``. In this + case, the :class:`~asynctest.Mock` object behavior is the same as with an + :class:`.Mock` object: the wrapped object may have methods + defined as coroutine functions. + """ + #: Property which is set when the mock is awaited. Its ``wait`` and + #: ``wait_next`` coroutine methods can be used to synchronize execution. + #: + #: .. versionadded:: 0.12 + awaited = _delegating_property('awaited') + #: Number of times the mock has been awaited. + #: + #: .. versionadded:: 0.12 + await_count = _delegating_property('await_count') + await_args = _delegating_property('await_args') + await_args_list = _delegating_property('await_args_list') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # asyncio.iscoroutinefunction() checks this property to say if an + # object is a coroutine + # It is set through __dict__ because when spec_set is True, this + # attribute is likely undefined. + self.__dict__['_is_coroutine'] = _is_coroutine + self.__dict__['_mock_awaited'] = _AwaitEvent(self) + self.__dict__['_mock_await_count'] = 0 + self.__dict__['_mock_await_args'] = None + self.__dict__['_mock_await_args_list'] = _CallList() + + def _mock_call(_mock_self, *args, **kwargs): + try: + result = super()._mock_call(*args, **kwargs) + _call = _mock_self.call_args + + @asyncio.coroutine + def proxy(): + try: + if _isawaitable(result): + return (yield from result) + else: + return result + finally: + _mock_self.await_count += 1 + _mock_self.await_args = _call + _mock_self.await_args_list.append(_call) + yield from _mock_self.awaited._notify() + + return proxy() + except StopIteration as e: + side_effect = _mock_self.side_effect + if side_effect is not None and not callable(side_effect): + raise + + return asyncio.coroutine(_raise)(e) + except BaseException as e: + return asyncio.coroutine(_raise)(e) + + def assert_awaited(_mock_self): + """ + Assert that the mock was awaited at least once. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_count == 0: + msg = ("Expected '%s' to have been awaited." % + self._mock_name or 'mock') + raise AssertionError(msg) + + def assert_awaited_once(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once. + + .. versionadded:: 0.12 + """ + self = _mock_self + if not self.await_count == 1: + msg = ("Expected '%s' to have been awaited once. Awaited %s times." % + (self._mock_name or 'mock', self.await_count)) + raise AssertionError(msg) + + def assert_awaited_with(_mock_self, *args, **kwargs): + """ + Assert that the last await was with the specified arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError('Expected await: %s\nNot awaited' % (expected,)) + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs) + return msg + + expected = self._call_matcher((args, kwargs)) + actual = self._call_matcher(self.await_args) + if expected != actual: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + def assert_awaited_once_with(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once and with the specified arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + if not self.await_count == 1: + msg = ("Expected '%s' to be awaited once. Awaited %s times." % + (self._mock_name or 'mock', self.await_count)) + raise AssertionError(msg) + return self.assert_awaited_with(*args, **kwargs) + + def assert_any_await(_mock_self, *args, **kwargs): + """ + Assert the mock has ever been awaited with the specified arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + expected = self._call_matcher((args, kwargs)) + actual = [self._call_matcher(c) for c in self.await_args_list] + if expected not in actual: + cause = expected if isinstance(expected, Exception) else None + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s await not found' % expected_string + ) from cause + + def assert_has_awaits(_mock_self, calls, any_order=False): + """ + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + + .. versionadded:: 0.12 + """ + self = _mock_self + expected = [self._call_matcher(c) for c in calls] + cause = expected if isinstance(expected, Exception) else None + all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) + if not any_order: + if expected not in all_awaits: + raise AssertionError( + 'Awaits not found.\nExpected: %r\n' + 'Actual: %r' % (_CallList(calls), self.await_args_list) + ) from cause + return + + all_awaits = list(all_awaits) + + not_found = [] + for kall in expected: + try: + all_awaits.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in await list' % (tuple(not_found),) + ) from cause + + def assert_not_awaited(_mock_self): + """ + Assert that the mock was never awaited. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_count != 0: + msg = ("Expected '%s' to not have been awaited. Awaited %s times." % + (self._mock_name or 'mock', self.await_count)) + raise AssertionError(msg) + + def reset_mock(self, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.awaited = _AwaitEvent(self) + self.await_count = 0 + self.await_args = None + self.await_args_list = _CallList() def _dot_lookup(thing, comp, import_path): try: @@ -1730,7 +2084,7 @@ def _patch_stopall(): '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', '__getstate__', '__setstate__', '__getformat__', '__setformat__', '__repr__', '__dir__', '__subclasses__', '__format__', - '__getnewargs_ex__', + '__getnewargs_ex__', '__aenter__', '__aexit__', '__anext__', '__aiter__', } @@ -2156,6 +2510,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec = type(spec) is_type = isinstance(spec, type) + is_coroutine_func = asyncio.iscoroutinefunction(spec) _kwargs = {'spec': spec} if spec_set: @@ -2173,6 +2528,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # descriptors don't have a spec # because we don't know what type they return _kwargs = {} + elif is_coroutine_func: + if instance: + raise RuntimeError("Instance can not be True when create_autospec " + "is mocking a coroutine function") + Klass = CoroutineMock elif not _callable(spec): Klass = NonCallableMagicMock elif is_type and instance and not _instance_callable(spec): From b83e5a5a7b3facbf5c9aa31d4dc94e6f1ad34a0c Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sun, 22 Jul 2018 16:26:02 -0700 Subject: [PATCH 02/24] Adding async support to the mock library. --- Lib/inspect.py | 10 +- Lib/unittest/mock.py | 642 +++++++++++++----------- Lib/unittest/test/testmock/testasync.py | 401 +++++++++++++++ Lib/unittest/test/testmock/testmock.py | 5 +- 4 files changed, 768 insertions(+), 290 deletions(-) create mode 100644 Lib/unittest/test/testmock/testasync.py diff --git a/Lib/inspect.py b/Lib/inspect.py index e799a83a74046f..c0b512f98c291a 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -176,13 +176,19 @@ def isgeneratorfunction(object): return bool((isfunction(object) or ismethod(object)) and object.__code__.co_flags & CO_GENERATOR) + +#### LISA this should be in another commit def iscoroutinefunction(object): """Return true if the object is a coroutine function. Coroutine functions are defined with "async def" syntax. """ - return bool((isfunction(object) or ismethod(object)) and - object.__code__.co_flags & CO_COROUTINE) + try: + return bool((isfunction(object) or ismethod(object)) and + object.__code__.co_flags & CO_COROUTINE) + except AttributeError: + pass # FIXME + def isasyncgenfunction(object): """Return true if the object is an asynchronous generator function. diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 3653e71a5ed8ae..65649054a9837e 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -30,7 +30,8 @@ import pprint import sys import builtins -from types import ModuleType +import fnmatch +from types import ModuleType, CodeType from functools import wraps, partial @@ -49,6 +50,11 @@ # Without this, the __class__ properties wouldn't be set correctly _safe_super = super + +def _is_coroutine_obj(obj): + return asyncio.iscoroutinefunction(obj) or asyncio.iscoroutine(obj) + + def _is_instance_mock(obj): # can't use isinstance on Mock objects because they override __class__ # The base class for all mocks is NonCallableMock @@ -358,57 +364,32 @@ def __init__(self, *args, **kwargs): pass -_is_coroutine = asyncio.iscoroutine -_isawaitable = inspect.isawaitable - -def _get_is_coroutine(self): - return self.__dict__['_mock_is_coroutine'] - - -def _set_is_coroutine(self, value): - # property setters and getters are overridden by Mock(), we need to - # update the dict to add values - value = _is_coroutine if bool(value) else False - self.__dict__['_mock_is_coroutine'] = value - - -class IsCoroutineMeta(type): - def __new__(meta, name, base, namespace): - namespace.update({ - '_get_is_coroutine': _get_is_coroutine, - '_set_is_coroutine': _set_is_coroutine, - 'is_coroutine': property(_get_is_coroutine, _set_is_coroutine, - doc="True if the object mocked is a coroutine"), - '_is_coroutine': property(_get_is_coroutine), - }) +_is_coroutine = asyncio.coroutines._is_coroutine - def __setattr__(self, name, value): - - if name == 'is_coroutine': - self._set_is_coroutine(value) - else: - return base[0].__setattr__(self, name, value) - - namespace['__setattr__'] = __setattr__ - - return super().__new__(meta, name, base, namespace) - - -class NonCallableMock(Base, metaclass=IsCoroutineMeta): +class NonCallableMock(Base): """A non-callable version of `Mock`""" def __new__(cls, *args, **kw): # every instance has its own class # so we can create magic methods on the # class without stomping on other mocks - new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) + bases = (cls,) + if cls != CoroutineMock: + # Check if spec is a coroutine object or function + sig = inspect.signature(NonCallableMock.__init__) + bound_args = sig.bind_partial(cls, *args, **kw).arguments + spec_arg = fnmatch.filter(bound_args.keys(), 'spec*') + if spec_arg: + # what if spec_set is different than spec? + if _is_coroutine_obj(bound_args[spec_arg[0]]): + bases = (CoroutineMockMixin, cls,) + new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = object.__new__(new) return instance - def __init__( self, spec=None, wraps=None, name=None, spec_set=None, - parent=None, is_coroutine=None, _spec_state=None, _new_name='', + parent=None, _spec_state=None, _new_name='', _new_parent=None, _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs ): @@ -451,8 +432,6 @@ def __init__( _spec_state ) - self._set_is_coroutine(is_coroutine) - def attach_mock(self, mock, attribute): """ Attach a mock as an attribute of this one, replacing its name and @@ -479,24 +458,29 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _eat_self=False): _spec_class = None _spec_signature = None + _spec_coroutines = [] + + for attr in dir(spec): + if asyncio.iscoroutinefunction(getattr(spec, attr)): + _spec_coroutines.append(attr) if spec is not None and not _is_list(spec): - if isinstance(spec, type): - _spec_class = spec - else: - _spec_class = _get_class(spec) - res = _get_signature_object(spec, - _spec_as_instance, _eat_self) - _spec_signature = res and res[1] + if isinstance(spec, type): + _spec_class = spec + else: + _spec_class = _get_class(spec) + res = _get_signature_object(spec, + _spec_as_instance, _eat_self) + _spec_signature = res and res[1] - spec = dir(spec) + spec = dir(spec) __dict__ = self.__dict__ __dict__['_spec_class'] = _spec_class __dict__['_spec_set'] = spec_set __dict__['_spec_signature'] = _spec_signature __dict__['_mock_methods'] = spec - + __dict__['_spec_coroutines'] = _spec_coroutines def __get_return_value(self): ret = self._mock_return_value @@ -929,12 +913,21 @@ def _get_child_mock(self, **kw): child mocks are made. For non-callable mocks the callable variant will be used (rather than - any custom subclass).""" + any custom subclass). + """ + _new_name = kw.get("_new_name") + if _new_name in self.__dict__['_spec_coroutines']: + return CoroutineMock(**kw) + _type = type(self) + if issubclass(_type, MagicMock) and _new_name in async_magic_coroutines: + klass = CoroutineMock + if issubclass(_type, CoroutineMockMixin): + klass = MagicMock if 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] @@ -1208,231 +1201,9 @@ class or instance) that acts as the specification for the mock object. If """ -class CoroutineMock(Mock): - """ - Enhance :class:`~asynctest.mock.Mock` with features allowing to mock - a coroutine function. - - The :class:`~asynctest.CoroutineMock` object will behave so the object is - recognized as coroutine function, and the result of a call as a coroutine: - - >>> mock = CoroutineMock() - >>> asyncio.iscoroutinefunction(mock) - True - >>> asyncio.iscoroutine(mock()) - True - - - The result of ``mock()`` is a coroutine which will have the outcome of - ``side_effect`` or ``return_value``: - - - if ``side_effect`` is a function, the coroutine will return the result - of that function, - - if ``side_effect`` is an exception, the coroutine will raise the - exception, - - if ``side_effect`` is an iterable, the coroutine will return the next - value of the iterable, however, if the sequence of result is exhausted, - ``StopIteration`` is raised immediately, - - if ``side_effect`` is not defined, the coroutine will return the value - defined by ``return_value``, hence, by default, the coroutine returns - a new :class:`~asynctest.CoroutineMock` object. - - If the outcome of ``side_effect`` or ``return_value`` is a coroutine, the - mock coroutine obtained when the mock object is called will be this - coroutine itself (and not a coroutine returning a coroutine). - - The test author can also specify a wrapped object with ``wraps``. In this - case, the :class:`~asynctest.Mock` object behavior is the same as with an - :class:`.Mock` object: the wrapped object may have methods - defined as coroutine functions. - """ - #: Property which is set when the mock is awaited. Its ``wait`` and - #: ``wait_next`` coroutine methods can be used to synchronize execution. - #: - #: .. versionadded:: 0.12 - awaited = _delegating_property('awaited') - #: Number of times the mock has been awaited. - #: - #: .. versionadded:: 0.12 - await_count = _delegating_property('await_count') - await_args = _delegating_property('await_args') - await_args_list = _delegating_property('await_args_list') - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # asyncio.iscoroutinefunction() checks this property to say if an - # object is a coroutine - # It is set through __dict__ because when spec_set is True, this - # attribute is likely undefined. - self.__dict__['_is_coroutine'] = _is_coroutine - self.__dict__['_mock_awaited'] = _AwaitEvent(self) - self.__dict__['_mock_await_count'] = 0 - self.__dict__['_mock_await_args'] = None - self.__dict__['_mock_await_args_list'] = _CallList() - - def _mock_call(_mock_self, *args, **kwargs): - try: - result = super()._mock_call(*args, **kwargs) - _call = _mock_self.call_args - - @asyncio.coroutine - def proxy(): - try: - if _isawaitable(result): - return (yield from result) - else: - return result - finally: - _mock_self.await_count += 1 - _mock_self.await_args = _call - _mock_self.await_args_list.append(_call) - yield from _mock_self.awaited._notify() - - return proxy() - except StopIteration as e: - side_effect = _mock_self.side_effect - if side_effect is not None and not callable(side_effect): - raise - - return asyncio.coroutine(_raise)(e) - except BaseException as e: - return asyncio.coroutine(_raise)(e) - - def assert_awaited(_mock_self): - """ - Assert that the mock was awaited at least once. - - .. versionadded:: 0.12 - """ - self = _mock_self - if self.await_count == 0: - msg = ("Expected '%s' to have been awaited." % - self._mock_name or 'mock') - raise AssertionError(msg) - - def assert_awaited_once(_mock_self, *args, **kwargs): - """ - Assert that the mock was awaited exactly once. - - .. versionadded:: 0.12 - """ - self = _mock_self - if not self.await_count == 1: - msg = ("Expected '%s' to have been awaited once. Awaited %s times." % - (self._mock_name or 'mock', self.await_count)) - raise AssertionError(msg) - - def assert_awaited_with(_mock_self, *args, **kwargs): - """ - Assert that the last await was with the specified arguments. - - .. versionadded:: 0.12 - """ - self = _mock_self - if self.await_args is None: - expected = self._format_mock_call_signature(args, kwargs) - raise AssertionError('Expected await: %s\nNot awaited' % (expected,)) - - def _error_message(): - msg = self._format_mock_failure_message(args, kwargs) - return msg +def _raise(exception): + raise exception - expected = self._call_matcher((args, kwargs)) - actual = self._call_matcher(self.await_args) - if expected != actual: - cause = expected if isinstance(expected, Exception) else None - raise AssertionError(_error_message()) from cause - - def assert_awaited_once_with(_mock_self, *args, **kwargs): - """ - Assert that the mock was awaited exactly once and with the specified arguments. - - .. versionadded:: 0.12 - """ - self = _mock_self - if not self.await_count == 1: - msg = ("Expected '%s' to be awaited once. Awaited %s times." % - (self._mock_name or 'mock', self.await_count)) - raise AssertionError(msg) - return self.assert_awaited_with(*args, **kwargs) - - def assert_any_await(_mock_self, *args, **kwargs): - """ - Assert the mock has ever been awaited with the specified arguments. - - .. versionadded:: 0.12 - """ - self = _mock_self - expected = self._call_matcher((args, kwargs)) - actual = [self._call_matcher(c) for c in self.await_args_list] - if expected not in actual: - cause = expected if isinstance(expected, Exception) else None - expected_string = self._format_mock_call_signature(args, kwargs) - raise AssertionError( - '%s await not found' % expected_string - ) from cause - - def assert_has_awaits(_mock_self, calls, any_order=False): - """ - Assert the mock has been awaited with the specified calls. - The :attr:`await_args_list` list is checked for the awaits. - - If `any_order` is False (the default) then the awaits must be - sequential. There can be extra calls before or after the - specified awaits. - - If `any_order` is True then the awaits can be in any order, but - they must all appear in :attr:`await_args_list`. - - .. versionadded:: 0.12 - """ - self = _mock_self - expected = [self._call_matcher(c) for c in calls] - cause = expected if isinstance(expected, Exception) else None - all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) - if not any_order: - if expected not in all_awaits: - raise AssertionError( - 'Awaits not found.\nExpected: %r\n' - 'Actual: %r' % (_CallList(calls), self.await_args_list) - ) from cause - return - - all_awaits = list(all_awaits) - - not_found = [] - for kall in expected: - try: - all_awaits.remove(kall) - except ValueError: - not_found.append(kall) - if not_found: - raise AssertionError( - '%r not all found in await list' % (tuple(not_found),) - ) from cause - - def assert_not_awaited(_mock_self): - """ - Assert that the mock was never awaited. - - .. versionadded:: 0.12 - """ - self = _mock_self - if self.await_count != 0: - msg = ("Expected '%s' to not have been awaited. Awaited %s times." % - (self._mock_name or 'mock', self.await_count)) - raise AssertionError(msg) - - def reset_mock(self, *args, **kwargs): - """ - See :func:`.Mock.reset_mock()` - """ - super().reset_mock(*args, **kwargs) - self.awaited = _AwaitEvent(self) - self.await_count = 0 - self.await_args = None - self.await_args_list = _CallList() def _dot_lookup(thing, comp, import_path): try: @@ -1631,8 +1402,10 @@ def __enter__(self): if isinstance(original, type): # If we're patching out a class and there is a spec inherit = True - - Klass = MagicMock + if spec is None and _is_coroutine_obj(original): + Klass = CoroutineMock + else: + Klass = MagicMock _kwargs = {} if new_callable is not None: Klass = new_callable @@ -1644,7 +1417,9 @@ def __enter__(self): not_callable = '__call__' not in this_spec else: not_callable = not callable(this_spec) - if not_callable: + if _is_coroutine_obj(this_spec): + Klass = CoroutineMock + elif not_callable: Klass = NonCallableMagicMock if spec is not None: @@ -2101,6 +1876,14 @@ def method(self, *args, **kw): ' '.join([magic_methods, numerics, inplace, right]).split() } +async_magic_coroutines = ("__aenter__", "__aexit__", "__anext__") +_async_magics = async_magic_coroutines + ("__aiter__", ) + +async_magic_coroutines = set(async_magic_coroutines) +_async_magics = set(_async_magics) + + + _all_magics = _magics | _non_defaults _unsupported_magics = { @@ -2221,7 +2004,6 @@ def _mock_set_magics(self): setattr(_type, entry, MagicProxy(entry, self)) - class NonCallableMagicMock(MagicMixin, NonCallableMock): """A version of `MagicMock` that isn't callable.""" def mock_add_spec(self, spec, spec_set=False): @@ -2234,7 +2016,6 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() - class MagicMock(MagicMixin, Mock): """ MagicMock is a subclass of Mock with default implementations @@ -2279,6 +2060,264 @@ def __get__(self, obj, _type=None): return self.create_mock() +class AsyncMagicMixin(object): + def __init__(self, *args, **kw): + self._mock_set_async_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)) + + +class CoroutineMockMixin(Base): + #: Property which is set when the mock is awaited. Its ``wait`` and + #: ``wait_next`` coroutine methods can be used to synchronize execution. + #: + #: .. versionadded:: 0.12 + awaited = _delegating_property('awaited') + #: Number of times the mock has been awaited. + #: + #: .. versionadded:: 0.12 + await_count = _delegating_property('await_count') + await_args = _delegating_property('await_args') + await_args_list = _delegating_property('await_args_list') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # asyncio.iscoroutinefunction() checks this property to say if an + # object is a coroutine + # It is set through __dict__ because when spec_set is True, this + # attribute is likely undefined. + self.__dict__['_is_coroutine'] = _is_coroutine + self.__dict__['_mock_awaited'] = _AwaitEvent(self) + self.__dict__['_mock_await_count'] = 0 + self.__dict__['_mock_await_args'] = None + self.__dict__['_mock_await_args_list'] = _CallList() + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = 0 + self.__dict__['__code__'] = code_mock + + def _mock_call(_mock_self, *args, **kwargs): + try: + result = super()._mock_call(*args, **kwargs) + _call = _mock_self.call_args + + @asyncio.coroutine + def proxy(): + try: + if asyncio.iscoroutine(result): + return (yield from result) + else: + return result + finally: + _mock_self.await_count += 1 + _mock_self.await_args = _call + _mock_self.await_args_list.append(_call) + yield from _mock_self.awaited._notify() + + return proxy() + except StopIteration as e: + side_effect = _mock_self.side_effect + if side_effect is not None and not callable(side_effect): + raise + + return asyncio.coroutine(_raise)(e) + except BaseException as e: + return asyncio.coroutine(_raise)(e) + + def assert_awaited(_mock_self): + """ + Assert that the mock was awaited at least once. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_count == 0: + msg = f"Expected {self._mock_name or 'mock'} to have been awaited." + raise AssertionError(msg) + + def assert_awaited_once(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once. + + .. versionadded:: 0.12 + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def assert_awaited_with(_mock_self, *args, **kwargs): + """ + Assert that the last await was with the specified arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_args is None: + expected = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'Expected await: {expected}\nNot awaited') + + def _error_message(): + msg = self._format_mock_failure_message(args, kwargs) + return msg + + expected = self._call_matcher((args, kwargs)) + actual = self._call_matcher(self.await_args) + if expected != actual: + cause = expected if isinstance(expected, Exception) else None + raise AssertionError(_error_message()) from cause + + def assert_awaited_once_with(_mock_self, *args, **kwargs): + """ + Assert that the mock was awaited exactly once and with the specified + arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + if not self.await_count == 1: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + return self.assert_awaited_with(*args, **kwargs) + + def assert_any_await(_mock_self, *args, **kwargs): + """ + Assert the mock has ever been awaited with the specified arguments. + + .. versionadded:: 0.12 + """ + self = _mock_self + expected = self._call_matcher((args, kwargs)) + actual = [self._call_matcher(c) for c in self.await_args_list] + if expected not in actual: + cause = expected if isinstance(expected, Exception) else None + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError( + '%s await not found' % expected_string + ) from cause + + def assert_has_awaits(_mock_self, calls, any_order=False): + """ + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + + .. versionadded:: 0.12 + """ + self = _mock_self + expected = [self._call_matcher(c) for c in calls] + cause = expected if isinstance(expected, Exception) else None + all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list) + if not any_order: + if expected not in all_awaits: + raise AssertionError( + f'Awaits not found.\nExpected: {_CallList(calls)}\n', + f'Actual: {self.await_args_list}' + ) from cause + return + + all_awaits = list(all_awaits) + + not_found = [] + for kall in expected: + try: + all_awaits.remove(kall) + except ValueError: + not_found.append(kall) + if not_found: + raise AssertionError( + '%r not all found in await list' % (tuple(not_found),) + ) from cause + + def assert_not_awaited(_mock_self): + """ + Assert that the mock was never awaited. + + .. versionadded:: 0.12 + """ + self = _mock_self + if self.await_count != 0: + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + f" Awaited {self.await_count} times.") + raise AssertionError(msg) + + def reset_mock(self, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.awaited = _AwaitEvent(self) + self.await_count = 0 + self.await_args = None + self.await_args_list = _CallList() + + +class CoroutineMock(CoroutineMockMixin, AsyncMagicMixin, Mock): + """ + Enhance :class:`Mock` with features allowing to mock + a coroutine function. + + The :class:`CoroutineMock` object will behave so the object is + recognized as coroutine function, and the result of a call as a coroutine: + + >>> mock = CoroutineMock() + >>> asyncio.iscoroutinefunction(mock) + True + >>> asyncio.iscoroutine(mock()) + True + + + The result of ``mock()`` is a coroutine which will have the outcome of + ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the coroutine will return the result + of that function, + - if ``side_effect`` is an exception, the coroutine will raise the + exception, + - if ``side_effect`` is an iterable, the coroutine will return the next + value of the iterable, however, if the sequence of result is exhausted, + ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the coroutine will return the value + defined by ``return_value``, hence, by default, the coroutine returns + a new :class:`CoroutineMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is a coroutine, the + mock coroutine obtained when the mock object is called will be this + coroutine itself (and not a coroutine returning a coroutine). + + The test author can also specify a wrapped object with ``wraps``. In this + case, the :class:`Mock` object behavior is the same as with an + :class:`.Mock` object: the wrapped object may have methods + defined as coroutine functions. + """ + class _ANY(object): "A helper object that compares equal to everything." @@ -2511,7 +2550,6 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, is_type = isinstance(spec, type) is_coroutine_func = asyncio.iscoroutinefunction(spec) - _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} @@ -2551,7 +2589,35 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if isinstance(spec, FunctionTypes): # should only happen at the top level because we don't # recurse for functions + + # LISA check if the orginal object is a coroutines + # if it is set the _is_coroutine to True + # somewhere around here + # We need to ensure that the child function is a coroutine function, + # without acting like a function mock = _set_signature(mock, spec) + if is_coroutine_func: + # Can't wrap the mock with asyncio.coroutine because it doesn't + # detect a CoroWrapper as an awaitable in debug mode. + # It is safe to do so because the mock object wrapped by + # _set_signature returns the result of the CoroutineMock itself, + # which is a Coroutine (as defined in CoroutineMock._mock_call) + mock._is_coroutine = _is_coroutine + mock.awaited = _AwaitEvent(mock) + mock.await_count = 0 + mock.await_args = None + mock.await_args_list = _CallList() + + for a in ('assert_awaited', + 'assert_awaited_once', + 'assert_awaited_with', + 'assert_awaited_once_with', + 'assert_any_await', + 'assert_has_awaits', + 'assert_not_awaited'): + def f(*args, **kwargs): + return getattr(wrapped_mock, a)(*args, **kwargs) + setattr(mock, a, f) else: _check_signature(spec, mock, is_type, instance) @@ -2595,9 +2661,13 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, skipfirst = _must_skip(spec, entry, is_type) kwargs['_eat_self'] = skipfirst - new = MagicMock(parent=parent, name=entry, _new_name=entry, - _new_parent=parent, - **kwargs) + if asyncio.iscoroutinefunction(original): + child_klass = CoroutineMock + else: + child_klass = MagicMock + new = child_klass(parent=parent, name=entry, _new_name=entry, + _new_parent=parent, + **kwargs) mock._mock_children[entry] = new _check_signature(original, new, skipfirst=skipfirst) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py new file mode 100644 index 00000000000000..2df3a02e41f091 --- /dev/null +++ b/Lib/unittest/test/testmock/testasync.py @@ -0,0 +1,401 @@ +import asyncio +import unittest + +from unittest.mock import call, CoroutineMock, patch, MagicMock + + +class AsyncFoo(object): + def __init__(self, a): + pass + async def coroutine_method(self): + pass + @asyncio.coroutine + def decorated_cr_method(self): + pass + +async def coroutine_func(): + pass + +class NormalFoo(object): + def a(self): + pass + + +async_foo_name = f'{__name__}.AsyncFoo' +normal_foo_name = f'{__name__}.NormalFoo' + + +def run_coroutine(coroutine): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + return loop.run_until_complete(coroutine) + finally: + loop.close() + + +class CoroutinePatchDecoratorTest(unittest.TestCase): + def test_is_coroutine_function_patch(self): + @patch.object(AsyncFoo, 'coroutine_method') + def test_async(mock_method): + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + + @patch.object(AsyncFoo, 'decorated_cr_method') + def test_async_decorator(mock_g): + self.assertTrue(asyncio.iscoroutinefunction(mock_g)) + + test_async() + test_async_decorator() + + def test_is_coroutine_patch(self): + @patch.object(AsyncFoo, 'coroutine_method') + def test_async(mock_method): + self.assertTrue(asyncio.iscoroutine(mock_method())) + + @patch.object(AsyncFoo, 'decorated_cr_method') + def test_async_decorator(mock_method): + self.assertTrue(asyncio.iscoroutine(mock_method())) + + @patch(f'{async_foo_name}.coroutine_method') + def test_no_parent_attribute(mock_method): + self.assertTrue(asyncio.iscoroutine(mock_method())) + + test_async() + test_async_decorator() + test_no_parent_attribute() + + def test_is_coroutinemock_patch(self): + @patch.object(AsyncFoo, 'coroutine_method') + def test_async(mock_method): + self.assertTrue(isinstance(mock_method, CoroutineMock)) + + @patch.object(AsyncFoo, 'decorated_cr_method') + def test_async_decorator(mock_method): + self.assertTrue(isinstance(mock_method, CoroutineMock)) + + test_async() + test_async_decorator() + + +class CoroutinePatchCMTest(unittest.TestCase): + def test_is_coroutine_function_cm(self): + def test_async(): + with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + + def test_async_decorator(): + with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + self.assertTrue(asyncio.iscoroutinefunction(mock_method)) + + test_async() + test_async_decorator() + + def test_is_coroutine_cm(self): + def test_async(): + with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + self.assertTrue(asyncio.iscoroutine(mock_method())) + + def test_async_decorator(): + with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + self.assertTrue(asyncio.iscoroutine(mock_method())) + test_async() + test_async_decorator() + + def test_is_coroutinemock_cm(self): + def test_async(): + with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + self.assertTrue(isinstance(mock_method, CoroutineMock)) + + def test_async_decorator(): + with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + self.assertTrue(isinstance(mock_method, CoroutineMock)) + + test_async() + test_async_decorator() + + +class CoroutineMockTest(unittest.TestCase): + def test_iscoroutinefunction(self): + mock = CoroutineMock() + self.assertTrue(asyncio.iscoroutinefunction(mock)) + + def test_iscoroutine(self): + mock = CoroutineMock() + self.assertTrue(asyncio.iscoroutine(mock())) + self.assertIn('assert_awaited', dir(mock)) + + # Should I test making a non-coroutine a coroutine mock? + + +class CoroutineAutospecTest(unittest.TestCase): + def test_is_coroutinemock_patch(self): + @patch(async_foo_name, autospec=True) + def test_async(mock_method): + self.assertTrue(isinstance( + mock_method.decorated_cr_method, + CoroutineMock)) + self.assertTrue(isinstance(mock_method, MagicMock)) + + @patch(async_foo_name, autospec=True) + def test_async_decorator(mock_method): + self.assertTrue(isinstance( + mock_method.decorated_cr_method, + CoroutineMock)) + self.assertTrue(isinstance(mock_method, MagicMock)) + + test_async() + test_async_decorator() + + +class CoroutineSpecTest(unittest.TestCase): + def test_spec_as_coroutine_positional(self): + mock = MagicMock(coroutine_func) + self.assertIsInstance(mock, MagicMock) # Is this what we want? + self.assertTrue(asyncio.iscoroutine(mock())) + + def test_spec_as_coroutine_kw(self): + mock = MagicMock(spec=coroutine_func) + self.assertIsInstance(mock, MagicMock) + self.assertTrue(asyncio.iscoroutine(mock())) + + def test_spec_coroutine_mock(self): + @patch.object(AsyncFoo, 'coroutine_method', spec=True) + def test_async(mock_method): + self.assertTrue(isinstance(mock_method, CoroutineMock)) + + test_async() + + def test_spec_parent_not_coroutine_attribute_is(self): + @patch(async_foo_name, spec=True) + def test_async(mock_method): + self.assertTrue(isinstance(mock_method, MagicMock)) + self.assertTrue(isinstance(mock_method.coroutine_method, + CoroutineMock)) + + test_async() + + def test_target_coroutine_spec_not(self): + @patch.object(AsyncFoo, 'coroutine_method', spec=NormalFoo.a) + def test_async_attribute(mock_method): + self.assertTrue(isinstance(mock_method, MagicMock)) + + test_async_attribute() + + def test_target_not_coroutine_spec_is(self): + @patch.object(NormalFoo, 'a', spec=coroutine_func) + def test_attribute_not_coroutine_spec_is(mock_coroutine_func): + self.assertTrue(isinstance(mock_coroutine_func, CoroutineMock)) + # should check here is there is coroutine functionality + test_attribute_not_coroutine_spec_is() + + def test_spec_coroutine_attributes(self): + @patch(normal_foo_name, spec=AsyncFoo) + def test_coroutine_attributes_coroutines(MockNormalFoo): + self.assertTrue(isinstance(MockNormalFoo.coroutine_method, + CoroutineMock)) + self.assertTrue(isinstance(MockNormalFoo, MagicMock)) + + test_coroutine_attributes_coroutines() + + +class CoroutineSpecSetTest(unittest.TestCase): + def test_is_coroutinemock_patch(self): + @patch.object(AsyncFoo, 'coroutine_method', spec_set=True) + def test_async(coroutine_method): + self.assertTrue(isinstance(coroutine_method, CoroutineMock)) + + def test_is_coroutine_coroutinemock(self): + mock = CoroutineMock(spec_set=AsyncFoo.coroutine_method) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(isinstance(mock, CoroutineMock)) + + def test_is_child_coroutinemock(self): + mock = MagicMock(spec_set=AsyncFoo) + self.assertTrue(asyncio.iscoroutinefunction(mock.coroutine_method)) + self.assertTrue(isinstance(mock.coroutine_method, CoroutineMock)) + self.assertTrue(isinstance(mock, MagicMock)) + + +class CoroutineMagicMethods(unittest.TestCase): + class AsyncContextManager: + def __init__(self): + self.entered = False + self.exited = False + + async def __aenter__(self, *args, **kwargs): + self.entered = True + return self + + async def __aexit__(self, *args, **kwargs): + self.exited = True + + class AsyncIterator: + def __init__(self): + self.iter_called = False + self.next_called = False + self.items = ["foo", "NormalFoo", "baz"] + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return self.items.pop() + except IndexError: + pass + + raise StopAsyncIteration + + def test_mock_magic_methods_are_coroutine_mocks(self): + mock_instance = CoroutineMock(spec=self.AsyncContextManager()) + self.assertIsInstance(mock_instance.__aenter__, + CoroutineMock) + self.assertIsInstance(mock_instance.__aexit__, + CoroutineMock) + + def test_mock_aiter_and_anext(self): + instance = self.AsyncIterator() + mock_instance = CoroutineMock(instance) + + self.assertEqual(asyncio.iscoroutine(instance.__aiter__), + asyncio.iscoroutine(mock_instance.__aiter__)) + self.assertEqual(asyncio.iscoroutine(instance.__anext__), + asyncio.iscoroutine(mock_instance.__anext__)) + + iterator = instance.__aiter__() + if asyncio.iscoroutine(iterator): + iterator = run_coroutine(iterator) + + mock_iterator = mock_instance.__aiter__() + if asyncio.iscoroutine(mock_iterator): + mock_iterator = run_coroutine(mock_iterator) + + self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), + asyncio.iscoroutine(mock_iterator.__aiter__)) + self.assertEqual(asyncio.iscoroutine(iterator.__anext__), + asyncio.iscoroutine(mock_iterator.__anext__)) + + +class CoroutineMockAssert(unittest.TestCase): + @asyncio.coroutine + def test_assert_awaited(self): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_awaited() + + yield from mock() + mock.assert_awaited() + + @asyncio.coroutine + def test_assert_awaited_once(self): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_awaited_once() + + yield from mock() + mock.assert_awaited_once() + + yield from mock() + with self.assertRaises(AssertionError): + mock.assert_awaited_once() + + @asyncio.coroutine + def test_assert_awaited_with(self): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_awaited_with('foo') + + yield from mock('foo') + mock.assert_awaited_with('foo') + + yield from mock('NormalFoo') + with self.assertRaises(AssertionError): + mock.assert_awaited_with('foo') + + @asyncio.coroutine + def test_assert_awaited_once_with(self): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_awaited_once_with('foo') + + yield from mock('foo') + mock.assert_awaited_once_with('foo') + + yield from mock('foo') + with self.assertRaises(AssertionError): + mock.assert_awaited_once_with('foo') + + @asyncio.coroutine + def test_assert_any_wait(self): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_any_await('NormalFoo') + + yield from mock('foo') + with self.assertRaises(AssertionError): + mock.assert_any_await('NormalFoo') + + yield from mock('NormalFoo') + mock.assert_any_await('NormalFoo') + + yield from mock('baz') + mock.assert_any_await('NormalFoo') + + @asyncio.coroutine + def test_assert_has_awaits(self): + calls = [call('NormalFoo'), call('baz')] + + with self.subTest('any_order=False'): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls) + + yield from mock('foo') + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls) + + yield from mock('NormalFoo') + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls) + + yield from mock('baz') + mock.assert_has_awaits(calls) + + yield from mock('qux') + mock.assert_has_awaits(calls) + + with self.subTest('any_order=True'): + mock = CoroutineMock() + + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls, any_order=True) + + yield from mock('baz') + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls, any_order=True) + + yield from mock('foo') + with self.assertRaises(AssertionError): + mock.assert_has_awaits(calls, any_order=True) + + yield from mock('NormalFoo') + mock.assert_has_awaits(calls, any_order=True) + + yield from mock('qux') + mock.assert_has_awaits(calls, any_order=True) + + @asyncio.coroutine + def test_assert_not_awaited(self): + mock = CoroutineMock() + + mock.assert_not_awaited() + + yield from mock() + with self.assertRaises(AssertionError): + mock.assert_not_awaited() diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index b64c8663d21226..75f174b8aa3928 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -8,7 +8,7 @@ from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, _CallList, + NonCallableMagicMock, CoroutineMock, _CallList, create_autospec ) @@ -1377,7 +1377,8 @@ def test_mock_add_spec_magic_methods(self): def test_adding_child_mock(self): - for Klass in NonCallableMock, Mock, MagicMock, NonCallableMagicMock: + for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, + CoroutineMock): mock = Klass() mock.foo = Mock() From a9ea983a3997a465183008b01d3f7027db6dbc19 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 15 Aug 2018 17:29:15 -0700 Subject: [PATCH 03/24] Removes superfluous changes. --- Lib/unittest/mock.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 65649054a9837e..ac3a06145428de 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -257,7 +257,7 @@ def __init__(self): def __getattr__(self, name): if name == '__bases__': - # Without this help() raises an exception + # Without this help(unittest.mock) raises an exception raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) @@ -465,15 +465,15 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_coroutines.append(attr) if spec is not None and not _is_list(spec): - if isinstance(spec, type): - _spec_class = spec - else: - _spec_class = _get_class(spec) - res = _get_signature_object(spec, - _spec_as_instance, _eat_self) - _spec_signature = res and res[1] + if isinstance(spec, type): + _spec_class = spec + else: + _spec_class = _get_class(spec) + res = _get_signature_object(spec, + _spec_as_instance, _eat_self) + _spec_signature = res and res[1] - spec = dir(spec) + spec = dir(spec) __dict__ = self.__dict__ __dict__['_spec_class'] = _spec_class @@ -913,8 +913,7 @@ def _get_child_mock(self, **kw): child mocks are made. For non-callable mocks the callable variant will be used (rather than - any custom subclass). - """ + any custom subclass).""" _new_name = kw.get("_new_name") if _new_name in self.__dict__['_spec_coroutines']: return CoroutineMock(**kw) @@ -1149,6 +1148,7 @@ def _mock_call(_mock_self, *args, **kwargs): return ret_val + class Mock(CallableMixin, NonCallableMock): """ Create a new `Mock` object. `Mock` takes several optional arguments @@ -2004,6 +2004,7 @@ def _mock_set_magics(self): setattr(_type, entry, MagicProxy(entry, self)) + class NonCallableMagicMock(MagicMixin, NonCallableMock): """A version of `MagicMock` that isn't callable.""" def mock_add_spec(self, spec, spec_set=False): @@ -2016,6 +2017,7 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() + class MagicMock(MagicMixin, Mock): """ MagicMock is a subclass of Mock with default implementations From 50581e3c92cf91a8406939ed79f0312937966c08 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 15 Aug 2018 17:50:02 -0700 Subject: [PATCH 04/24] Cleans up comments. --- Lib/inspect.py | 3 +-- Lib/unittest/mock.py | 21 --------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index c0b512f98c291a..90d331e9d48cd3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -177,7 +177,6 @@ def isgeneratorfunction(object): object.__code__.co_flags & CO_GENERATOR) -#### LISA this should be in another commit def iscoroutinefunction(object): """Return true if the object is a coroutine function. @@ -187,7 +186,7 @@ def iscoroutinefunction(object): return bool((isfunction(object) or ismethod(object)) and object.__code__.co_flags & CO_COROUTINE) except AttributeError: - pass # FIXME + pass def isasyncgenfunction(object): diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index ac3a06145428de..3b3c8e9ddb29b5 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2089,14 +2089,7 @@ def _mock_set_async_magics(self): class CoroutineMockMixin(Base): - #: Property which is set when the mock is awaited. Its ``wait`` and - #: ``wait_next`` coroutine methods can be used to synchronize execution. - #: - #: .. versionadded:: 0.12 awaited = _delegating_property('awaited') - #: Number of times the mock has been awaited. - #: - #: .. versionadded:: 0.12 await_count = _delegating_property('await_count') await_args = _delegating_property('await_args') await_args_list = _delegating_property('await_args_list') @@ -2148,8 +2141,6 @@ def proxy(): def assert_awaited(_mock_self): """ Assert that the mock was awaited at least once. - - .. versionadded:: 0.12 """ self = _mock_self if self.await_count == 0: @@ -2159,8 +2150,6 @@ def assert_awaited(_mock_self): def assert_awaited_once(_mock_self, *args, **kwargs): """ Assert that the mock was awaited exactly once. - - .. versionadded:: 0.12 """ self = _mock_self if not self.await_count == 1: @@ -2171,8 +2160,6 @@ def assert_awaited_once(_mock_self, *args, **kwargs): def assert_awaited_with(_mock_self, *args, **kwargs): """ Assert that the last await was with the specified arguments. - - .. versionadded:: 0.12 """ self = _mock_self if self.await_args is None: @@ -2193,8 +2180,6 @@ def assert_awaited_once_with(_mock_self, *args, **kwargs): """ Assert that the mock was awaited exactly once and with the specified arguments. - - .. versionadded:: 0.12 """ self = _mock_self if not self.await_count == 1: @@ -2206,8 +2191,6 @@ def assert_awaited_once_with(_mock_self, *args, **kwargs): def assert_any_await(_mock_self, *args, **kwargs): """ Assert the mock has ever been awaited with the specified arguments. - - .. versionadded:: 0.12 """ self = _mock_self expected = self._call_matcher((args, kwargs)) @@ -2230,8 +2213,6 @@ def assert_has_awaits(_mock_self, calls, any_order=False): If `any_order` is True then the awaits can be in any order, but they must all appear in :attr:`await_args_list`. - - .. versionadded:: 0.12 """ self = _mock_self expected = [self._call_matcher(c) for c in calls] @@ -2261,8 +2242,6 @@ def assert_has_awaits(_mock_self, calls, any_order=False): def assert_not_awaited(_mock_self): """ Assert that the mock was never awaited. - - .. versionadded:: 0.12 """ self = _mock_self if self.await_count != 0: From 96ddb0e32945f835f01a16f6969d533ea9952b77 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Mon, 10 Sep 2018 15:49:04 -0700 Subject: [PATCH 05/24] Fixes inspect and attribute error issues. --- Lib/inspect.py | 7 ++----- Lib/unittest/mock.py | 20 +++++++++++++++----- Lib/unittest/test/testmock/testasync.py | 10 +++++++++- Lib/unittest/test/testmock/testcallable.py | 7 +++++++ Lib/unittest/test/testmock/testhelpers.py | 1 - 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 90d331e9d48cd3..d3931ba0deb9e7 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -182,11 +182,8 @@ def iscoroutinefunction(object): Coroutine functions are defined with "async def" syntax. """ - try: - return bool((isfunction(object) or ismethod(object)) and - object.__code__.co_flags & CO_COROUTINE) - except AttributeError: - pass + return bool((isfunction(object) or ismethod(object)) and + object.__code__.co_flags & CO_COROUTINE) def isasyncgenfunction(object): diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 3b3c8e9ddb29b5..43f116c3fa7b5b 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -52,7 +52,10 @@ def _is_coroutine_obj(obj): - return asyncio.iscoroutinefunction(obj) or asyncio.iscoroutine(obj) + if getattr(obj, '__code__', None): + return asyncio.iscoroutinefunction(obj) or asyncio.iscoroutine(obj) + else: + return False def _is_instance_mock(obj): @@ -461,7 +464,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_coroutines = [] for attr in dir(spec): - if asyncio.iscoroutinefunction(getattr(spec, attr)): + if asyncio.iscoroutinefunction(getattr(spec, attr, None)): _spec_coroutines.append(attr) if spec is not None and not _is_list(spec): @@ -1054,7 +1057,10 @@ def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, **kwargs): self.__dict__['_mock_return_value'] = return_value - + # Makes inspect.iscoroutinefunction() return False when testing a Mock. + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = 0 + self.__dict__['__code__'] = code_mock _safe_super(CallableMixin, self).__init__( spec, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, **kwargs @@ -2107,9 +2113,10 @@ def __init__(self, *args, **kwargs): self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = _CallList() code_mock = NonCallableMock(spec_set=CodeType) - code_mock.co_flags = 0 + code_mock.co_flags = 129 self.__dict__['__code__'] = code_mock + def _mock_call(_mock_self, *args, **kwargs): try: result = super()._mock_call(*args, **kwargs) @@ -2530,7 +2537,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec = type(spec) is_type = isinstance(spec, type) - is_coroutine_func = asyncio.iscoroutinefunction(spec) + if getattr(spec, '__code__', None): + is_coroutine_func = asyncio.iscoroutinefunction(spec) + else: + is_coroutine_func = False _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 2df3a02e41f091..e5a33c2a2bcf0c 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -1,4 +1,5 @@ import asyncio +import inspect import unittest from unittest.mock import call, CoroutineMock, patch, MagicMock @@ -115,10 +116,16 @@ def test_async_decorator(): class CoroutineMockTest(unittest.TestCase): - def test_iscoroutinefunction(self): + def test_iscoroutinefunction_default(self): mock = CoroutineMock() self.assertTrue(asyncio.iscoroutinefunction(mock)) + def test_iscoroutinefunction_function(self): + async def foo(): pass + mock = CoroutineMock(foo) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) + def test_iscoroutine(self): mock = CoroutineMock() self.assertTrue(asyncio.iscoroutine(mock())) @@ -178,6 +185,7 @@ def test_target_coroutine_spec_not(self): @patch.object(AsyncFoo, 'coroutine_method', spec=NormalFoo.a) def test_async_attribute(mock_method): self.assertTrue(isinstance(mock_method, MagicMock)) + self.assertFalse(inspect.iscoroutinefunction(mock_method)) test_async_attribute() diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index af1ce7ebbae4da..0f0d99f43a7db2 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -2,6 +2,7 @@ # E-mail: fuzzyman AT voidspace DOT org DOT uk # http://www.voidspace.org.uk/python/mock/ +import inspect import unittest from unittest.test.testmock.support import is_instance, X, SomeClass @@ -146,6 +147,12 @@ def test_create_autospec_instance(self): self.assertRaises(TypeError, mock.wibble, 'some', 'args') + def test_inspect_iscoroutine_function(self): + def foo(): pass + + mock = Mock(foo) + self.assertFalse(inspect.iscoroutine(mock)) + if __name__ == "__main__": unittest.main() diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 7919482ae99c7f..2e19a60001aacd 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -698,7 +698,6 @@ class RaiserClass(object): @staticmethod def existing(a, b): return a + b - s = create_autospec(RaiserClass) self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) s.existing(1, 2) From a4d4dbceefd66b3485679d5b2dc13ef19b56ce49 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 11 Sep 2018 15:59:23 -0700 Subject: [PATCH 06/24] Fixes test_unittest changing env because of version issue. --- Lib/unittest/mock.py | 32 +++++++++++++++++++++++++ Lib/unittest/test/testmock/testasync.py | 29 +++++++++++++--------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 43f116c3fa7b5b..8b45a7e08fc494 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -1920,6 +1920,22 @@ def method(self, *args, **kw): '__index__': 1, } +class AsyncIterator: + """ + Wraps an iterator in an asynchronous iterator. + """ + def __init__(self, iterator): + self.iterator = iterator + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self.iterator) + except StopIteration: + pass + raise StopAsyncIteration def _get_eq(self): def __eq__(other): @@ -1950,10 +1966,26 @@ def __iter__(): return iter(ret_val) return __iter__ +def _get_async_iter(mock): + def __aiter__(): + return_value = mock.__aiter__._mock_return_value + if return_value is DEFAULT: + iterator = iter([]) + else: + iterator = iter(return_value) + + return AsyncIterator(iterator) + + if asyncio.iscoroutinefunction(mock.__aiter__): + return asyncio.coroutine(__aiter__) + + return __aiter__ + _side_effect_methods = { '__eq__': _get_eq, '__ne__': _get_ne, '__iter__': _get_iter, + '__aiter__': _get_async_iter } diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index e5a33c2a2bcf0c..80bddf43e78839 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -1,5 +1,6 @@ import asyncio import inspect +import sys import unittest from unittest.mock import call, CoroutineMock, patch, MagicMock @@ -237,7 +238,7 @@ async def __aenter__(self, *args, **kwargs): async def __aexit__(self, *args, **kwargs): self.exited = True - class AsyncIterator: + class AsyncIterator(object): def __init__(self): self.iter_called = False self.next_called = False @@ -254,6 +255,11 @@ async def __anext__(self): raise StopAsyncIteration + # Before 3.7 __aiter__ was a coroutine + class AsyncItertorDeprecated(AsyncIterator): + async def __aiter__(self): + return super().__aiter__() + def test_mock_magic_methods_are_coroutine_mocks(self): mock_instance = CoroutineMock(spec=self.AsyncContextManager()) self.assertIsInstance(mock_instance.__aenter__, @@ -262,26 +268,27 @@ def test_mock_magic_methods_are_coroutine_mocks(self): CoroutineMock) def test_mock_aiter_and_anext(self): - instance = self.AsyncIterator() + if sys.version_info < (3, 7): + instance = self.AsyncItertorDeprecated() + else: + instance = self.AsyncIterator() mock_instance = CoroutineMock(instance) self.assertEqual(asyncio.iscoroutine(instance.__aiter__), asyncio.iscoroutine(mock_instance.__aiter__)) self.assertEqual(asyncio.iscoroutine(instance.__anext__), asyncio.iscoroutine(mock_instance.__anext__)) - - iterator = instance.__aiter__() - if asyncio.iscoroutine(iterator): + if sys.version_info < (3, 7): + iterator = instance.__aiter__() iterator = run_coroutine(iterator) - mock_iterator = mock_instance.__aiter__() - if asyncio.iscoroutine(mock_iterator): + mock_iterator = mock_instance.__aiter__() mock_iterator = run_coroutine(mock_iterator) - self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), - asyncio.iscoroutine(mock_iterator.__aiter__)) - self.assertEqual(asyncio.iscoroutine(iterator.__anext__), - asyncio.iscoroutine(mock_iterator.__anext__)) + self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), + asyncio.iscoroutine(mock_iterator.__aiter__)) + self.assertEqual(asyncio.iscoroutine(iterator.__anext__), + asyncio.iscoroutine(mock_iterator.__anext__)) class CoroutineMockAssert(unittest.TestCase): From bfdd5a7428a1e7d5738ba9f98f7a5c78342f72d6 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 12 Sep 2018 09:02:37 -0700 Subject: [PATCH 07/24] Removes newlines from inspect. --- Lib/inspect.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index d3931ba0deb9e7..e799a83a74046f 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -176,7 +176,6 @@ def isgeneratorfunction(object): return bool((isfunction(object) or ismethod(object)) and object.__code__.co_flags & CO_GENERATOR) - def iscoroutinefunction(object): """Return true if the object is a coroutine function. @@ -185,7 +184,6 @@ def iscoroutinefunction(object): return bool((isfunction(object) or ismethod(object)) and object.__code__.co_flags & CO_COROUTINE) - def isasyncgenfunction(object): """Return true if the object is an asynchronous generator function. From ed7f13cd15410b8cb3b6942ecc1640a8a99e3f81 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 12 Sep 2018 09:07:31 -0700 Subject: [PATCH 08/24] Removes unneeded comment and newlines. --- Lib/unittest/mock.py | 7 ------- Lib/unittest/test/testmock/testhelpers.py | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 8b45a7e08fc494..dfde1a5389ea8c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -50,7 +50,6 @@ # Without this, the __class__ properties wouldn't be set correctly _safe_super = super - def _is_coroutine_obj(obj): if getattr(obj, '__code__', None): return asyncio.iscoroutinefunction(obj) or asyncio.iscoroutine(obj) @@ -2612,12 +2611,6 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if isinstance(spec, FunctionTypes): # should only happen at the top level because we don't # recurse for functions - - # LISA check if the orginal object is a coroutines - # if it is set the _is_coroutine to True - # somewhere around here - # We need to ensure that the child function is a coroutine function, - # without acting like a function mock = _set_signature(mock, spec) if is_coroutine_func: # Can't wrap the mock with asyncio.coroutine because it doesn't diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 2e19a60001aacd..7919482ae99c7f 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -698,6 +698,7 @@ class RaiserClass(object): @staticmethod def existing(a, b): return a + b + s = create_autospec(RaiserClass) self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) s.existing(1, 2) From 34fa74ef7fb951f991692f412e9c599b112aa75b Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Thu, 13 Sep 2018 20:38:21 -0700 Subject: [PATCH 09/24] Fixes async tests. Removes inspect fix. --- Lib/unittest/mock.py | 11 +- Lib/unittest/test/testmock/testasync.py | 250 ++++++++++-------- Lib/unittest/test/testmock/testcallable.py | 7 - .../2018-09-13-20-33-24.bpo-26467.cahAk3.rst | 2 + 4 files changed, 144 insertions(+), 126 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index dfde1a5389ea8c..fea43b71e88719 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -391,9 +391,8 @@ def __new__(cls, *args, **kw): def __init__( self, spec=None, wraps=None, name=None, spec_set=None, - parent=None, _spec_state=None, _new_name='', - _new_parent=None, _spec_as_instance=False, _eat_self=None, - unsafe=False, **kwargs + parent=None, _spec_state=None, _new_name='', _new_parent=None, + _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs ): if _new_parent is None: _new_parent = parent @@ -928,7 +927,7 @@ def _get_child_mock(self, **kw): if 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] @@ -1056,10 +1055,6 @@ def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, **kwargs): self.__dict__['_mock_return_value'] = return_value - # Makes inspect.iscoroutinefunction() return False when testing a Mock. - code_mock = NonCallableMock(spec_set=CodeType) - code_mock.co_flags = 0 - self.__dict__['__code__'] = code_mock _safe_super(CallableMixin, self).__init__( spec, wraps, name, spec_set, parent, _spec_state, _new_name, _new_parent, **kwargs diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 80bddf43e78839..b6ae5de7c65884 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -6,7 +6,7 @@ from unittest.mock import call, CoroutineMock, patch, MagicMock -class AsyncFoo(object): +class AsyncClass(object): def __init__(self, a): pass async def coroutine_method(self): @@ -14,17 +14,22 @@ async def coroutine_method(self): @asyncio.coroutine def decorated_cr_method(self): pass + def normal_method(self): + pass async def coroutine_func(): pass -class NormalFoo(object): +def normal_func(): + pass + +class NormalClass(object): def a(self): pass -async_foo_name = f'{__name__}.AsyncFoo' -normal_foo_name = f'{__name__}.NormalFoo' +async_foo_name = f'{__name__}.AsyncClass' +normal_foo_name = f'{__name__}.NormalClass' def run_coroutine(coroutine): @@ -38,11 +43,11 @@ def run_coroutine(coroutine): class CoroutinePatchDecoratorTest(unittest.TestCase): def test_is_coroutine_function_patch(self): - @patch.object(AsyncFoo, 'coroutine_method') + @patch.object(AsyncClass, 'coroutine_method') def test_async(mock_method): self.assertTrue(asyncio.iscoroutinefunction(mock_method)) - @patch.object(AsyncFoo, 'decorated_cr_method') + @patch.object(AsyncClass, 'decorated_cr_method') def test_async_decorator(mock_g): self.assertTrue(asyncio.iscoroutinefunction(mock_g)) @@ -50,11 +55,11 @@ def test_async_decorator(mock_g): test_async_decorator() def test_is_coroutine_patch(self): - @patch.object(AsyncFoo, 'coroutine_method') + @patch.object(AsyncClass, 'coroutine_method') def test_async(mock_method): self.assertTrue(asyncio.iscoroutine(mock_method())) - @patch.object(AsyncFoo, 'decorated_cr_method') + @patch.object(AsyncClass, 'decorated_cr_method') def test_async_decorator(mock_method): self.assertTrue(asyncio.iscoroutine(mock_method())) @@ -67,11 +72,11 @@ def test_no_parent_attribute(mock_method): test_no_parent_attribute() def test_is_coroutinemock_patch(self): - @patch.object(AsyncFoo, 'coroutine_method') + @patch.object(AsyncClass, 'coroutine_method') def test_async(mock_method): self.assertTrue(isinstance(mock_method, CoroutineMock)) - @patch.object(AsyncFoo, 'decorated_cr_method') + @patch.object(AsyncClass, 'decorated_cr_method') def test_async_decorator(mock_method): self.assertTrue(isinstance(mock_method, CoroutineMock)) @@ -82,11 +87,11 @@ def test_async_decorator(mock_method): class CoroutinePatchCMTest(unittest.TestCase): def test_is_coroutine_function_cm(self): def test_async(): - with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + with patch.object(AsyncClass, 'coroutine_method') as mock_method: self.assertTrue(asyncio.iscoroutinefunction(mock_method)) def test_async_decorator(): - with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: self.assertTrue(asyncio.iscoroutinefunction(mock_method)) test_async() @@ -94,22 +99,22 @@ def test_async_decorator(): def test_is_coroutine_cm(self): def test_async(): - with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + with patch.object(AsyncClass, 'coroutine_method') as mock_method: self.assertTrue(asyncio.iscoroutine(mock_method())) def test_async_decorator(): - with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: self.assertTrue(asyncio.iscoroutine(mock_method())) test_async() test_async_decorator() def test_is_coroutinemock_cm(self): def test_async(): - with patch.object(AsyncFoo, 'coroutine_method') as mock_method: + with patch.object(AsyncClass, 'coroutine_method') as mock_method: self.assertTrue(isinstance(mock_method, CoroutineMock)) def test_async_decorator(): - with patch.object(AsyncFoo, 'decorated_cr_method') as mock_method: + with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: self.assertTrue(isinstance(mock_method, CoroutineMock)) test_async() @@ -132,7 +137,11 @@ def test_iscoroutine(self): self.assertTrue(asyncio.iscoroutine(mock())) self.assertIn('assert_awaited', dir(mock)) - # Should I test making a non-coroutine a coroutine mock? + def test_iscoroutinefunction_normal_function(self): + def foo(): pass + mock = CoroutineMock(foo) + self.assertTrue(asyncio.iscoroutinefunction(mock)) + self.assertTrue(inspect.iscoroutinefunction(mock)) class CoroutineAutospecTest(unittest.TestCase): @@ -140,7 +149,7 @@ def test_is_coroutinemock_patch(self): @patch(async_foo_name, autospec=True) def test_async(mock_method): self.assertTrue(isinstance( - mock_method.decorated_cr_method, + mock_method.coroutine_method, CoroutineMock)) self.assertTrue(isinstance(mock_method, MagicMock)) @@ -151,23 +160,50 @@ def test_async_decorator(mock_method): CoroutineMock)) self.assertTrue(isinstance(mock_method, MagicMock)) + @patch(async_foo_name, autospec=True) + def test_normal_method(mock_method): + self.assertTrue(isinstance( + mock_method.normal_method, + MagicMock)) + test_async() test_async_decorator() + test_normal_method() class CoroutineSpecTest(unittest.TestCase): - def test_spec_as_coroutine_positional(self): + def test_spec_as_coroutine_positional_magicmock(self): mock = MagicMock(coroutine_func) - self.assertIsInstance(mock, MagicMock) # Is this what we want? + self.assertIsInstance(mock, MagicMock) self.assertTrue(asyncio.iscoroutine(mock())) - def test_spec_as_coroutine_kw(self): + def test_spec_as_coroutine_kw_magicmock(self): mock = MagicMock(spec=coroutine_func) self.assertIsInstance(mock, MagicMock) self.assertTrue(asyncio.iscoroutine(mock())) + def test_spec_as_coroutine_kw_coroutinemock(self): + mock = CoroutineMock(spec=coroutine_func) + self.assertIsInstance(mock, CoroutineMock) + self.assertTrue(asyncio.iscoroutine(mock())) + + def test_spec_as_coroutine_positional_coroutinemock(self): + mock = CoroutineMock(coroutine_func) + self.assertIsInstance(mock, CoroutineMock) + self.assertTrue(asyncio.iscoroutine(mock())) + + def test_spec_as_normal_kw_coroutinemock(self): + mock = CoroutineMock(spec=normal_func) + self.assertIsInstance(mock, CoroutineMock) + self.assertTrue(asyncio.iscoroutine(mock())) + + def test_spec_as_normal_positional_coroutinemock(self): + mock = CoroutineMock(normal_func) + self.assertIsInstance(mock, CoroutineMock) + self.assertTrue(asyncio.iscoroutine(mock())) + def test_spec_coroutine_mock(self): - @patch.object(AsyncFoo, 'coroutine_method', spec=True) + @patch.object(AsyncClass, 'coroutine_method', spec=True) def test_async(mock_method): self.assertTrue(isinstance(mock_method, CoroutineMock)) @@ -183,45 +219,49 @@ def test_async(mock_method): test_async() def test_target_coroutine_spec_not(self): - @patch.object(AsyncFoo, 'coroutine_method', spec=NormalFoo.a) + @patch.object(AsyncClass, 'coroutine_method', spec=NormalClass.a) def test_async_attribute(mock_method): self.assertTrue(isinstance(mock_method, MagicMock)) - self.assertFalse(inspect.iscoroutinefunction(mock_method)) + self.assertFalse(inspect.iscoroutine(mock_method)) + self.assertFalse(asyncio.iscoroutine(mock_method)) test_async_attribute() def test_target_not_coroutine_spec_is(self): - @patch.object(NormalFoo, 'a', spec=coroutine_func) + @patch.object(NormalClass, 'a', spec=coroutine_func) def test_attribute_not_coroutine_spec_is(mock_coroutine_func): self.assertTrue(isinstance(mock_coroutine_func, CoroutineMock)) - # should check here is there is coroutine functionality test_attribute_not_coroutine_spec_is() def test_spec_coroutine_attributes(self): - @patch(normal_foo_name, spec=AsyncFoo) - def test_coroutine_attributes_coroutines(MockNormalFoo): - self.assertTrue(isinstance(MockNormalFoo.coroutine_method, + @patch(normal_foo_name, spec=AsyncClass) + def test_coroutine_attributes_coroutines(MockNormalClass): + self.assertTrue(isinstance(MockNormalClass.coroutine_method, CoroutineMock)) - self.assertTrue(isinstance(MockNormalFoo, MagicMock)) + self.assertTrue(isinstance(MockNormalClass, MagicMock)) test_coroutine_attributes_coroutines() class CoroutineSpecSetTest(unittest.TestCase): def test_is_coroutinemock_patch(self): - @patch.object(AsyncFoo, 'coroutine_method', spec_set=True) + @patch.object(AsyncClass, 'coroutine_method', spec_set=True) def test_async(coroutine_method): self.assertTrue(isinstance(coroutine_method, CoroutineMock)) def test_is_coroutine_coroutinemock(self): - mock = CoroutineMock(spec_set=AsyncFoo.coroutine_method) + mock = CoroutineMock(spec_set=AsyncClass.coroutine_method) self.assertTrue(asyncio.iscoroutinefunction(mock)) self.assertTrue(isinstance(mock, CoroutineMock)) def test_is_child_coroutinemock(self): - mock = MagicMock(spec_set=AsyncFoo) + mock = MagicMock(spec_set=AsyncClass) self.assertTrue(asyncio.iscoroutinefunction(mock.coroutine_method)) + self.assertTrue(asyncio.iscoroutinefunction(mock.decorated_cr_method)) + self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) self.assertTrue(isinstance(mock.coroutine_method, CoroutineMock)) + self.assertTrue(isinstance(mock.decorated_cr_method, CoroutineMock)) + self.assertTrue(isinstance(mock.normal_method, MagicMock)) self.assertTrue(isinstance(mock, MagicMock)) @@ -292,125 +332,113 @@ def test_mock_aiter_and_anext(self): class CoroutineMockAssert(unittest.TestCase): - @asyncio.coroutine - def test_assert_awaited(self): - mock = CoroutineMock() + def setUp(self): + self.mock = CoroutineMock() + + async def runnable_test(self, *args): + if not args: + await self.mock() + else: + await self.mock(*args) + + def test_assert_awaited(self): with self.assertRaises(AssertionError): - mock.assert_awaited() + self.mock.assert_awaited() - yield from mock() - mock.assert_awaited() + asyncio.run(self.runnable_test()) + self.mock.assert_awaited() - @asyncio.coroutine def test_assert_awaited_once(self): - mock = CoroutineMock() - with self.assertRaises(AssertionError): - mock.assert_awaited_once() + self.mock.assert_awaited_once() - yield from mock() - mock.assert_awaited_once() + asyncio.run(self.runnable_test()) + self.mock.assert_awaited_once() - yield from mock() + asyncio.run(self.runnable_test()) with self.assertRaises(AssertionError): - mock.assert_awaited_once() + self.mock.assert_awaited_once() - @asyncio.coroutine def test_assert_awaited_with(self): - mock = CoroutineMock() - + asyncio.run(self.runnable_test()) with self.assertRaises(AssertionError): - mock.assert_awaited_with('foo') + self.mock.assert_awaited_with('foo') - yield from mock('foo') - mock.assert_awaited_with('foo') + asyncio.run(self.runnable_test('foo')) + self.mock.assert_awaited_with('foo') - yield from mock('NormalFoo') + asyncio.run(self.runnable_test('SomethingElse')) with self.assertRaises(AssertionError): - mock.assert_awaited_with('foo') + self.mock.assert_awaited_with('foo') - @asyncio.coroutine def test_assert_awaited_once_with(self): - mock = CoroutineMock() - with self.assertRaises(AssertionError): - mock.assert_awaited_once_with('foo') + self.mock.assert_awaited_once_with('foo') - yield from mock('foo') - mock.assert_awaited_once_with('foo') + asyncio.run(self.runnable_test('foo')) + self.mock.assert_awaited_once_with('foo') - yield from mock('foo') + asyncio.run(self.runnable_test('foo')) with self.assertRaises(AssertionError): - mock.assert_awaited_once_with('foo') + self.mock.assert_awaited_once_with('foo') - @asyncio.coroutine def test_assert_any_wait(self): - mock = CoroutineMock() - with self.assertRaises(AssertionError): - mock.assert_any_await('NormalFoo') + self.mock.assert_any_await('NormalFoo') - yield from mock('foo') + asyncio.run(self.runnable_test('foo')) with self.assertRaises(AssertionError): - mock.assert_any_await('NormalFoo') + self.mock.assert_any_await('NormalFoo') - yield from mock('NormalFoo') - mock.assert_any_await('NormalFoo') + asyncio.run(self.runnable_test('NormalFoo')) + self.mock.assert_any_await('NormalFoo') - yield from mock('baz') - mock.assert_any_await('NormalFoo') + asyncio.run(self.runnable_test('SomethingElse')) + self.mock.assert_any_await('NormalFoo') - @asyncio.coroutine - def test_assert_has_awaits(self): + def test_assert_has_awaits_no_order(self): calls = [call('NormalFoo'), call('baz')] - with self.subTest('any_order=False'): - mock = CoroutineMock() - - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls) - - yield from mock('foo') - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) - yield from mock('NormalFoo') - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls) + asyncio.run(self.runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) - yield from mock('baz') - mock.assert_has_awaits(calls) + asyncio.run(self.runnable_test('NormalFoo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls) - yield from mock('qux') - mock.assert_has_awaits(calls) + asyncio.run(self.runnable_test('baz')) + self.mock.assert_has_awaits(calls) - with self.subTest('any_order=True'): - mock = CoroutineMock() + asyncio.run(self.runnable_test('SomethingElse')) + self.mock.assert_has_awaits(calls) - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls, any_order=True) + def test_assert_has_awaits_ordered(self): + calls = [call('NormalFoo'), call('baz')] + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) - yield from mock('baz') - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls, any_order=True) + asyncio.run(self.runnable_test('baz')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) - yield from mock('foo') - with self.assertRaises(AssertionError): - mock.assert_has_awaits(calls, any_order=True) + asyncio.run(self.runnable_test('foo')) + with self.assertRaises(AssertionError): + self.mock.assert_has_awaits(calls, any_order=True) - yield from mock('NormalFoo') - mock.assert_has_awaits(calls, any_order=True) + asyncio.run(self.runnable_test('NormalFoo')) + self.mock.assert_has_awaits(calls, any_order=True) - yield from mock('qux') - mock.assert_has_awaits(calls, any_order=True) + asyncio.run(self.runnable_test('qux')) + self.mock.assert_has_awaits(calls, any_order=True) - @asyncio.coroutine def test_assert_not_awaited(self): - mock = CoroutineMock() - - mock.assert_not_awaited() + self.mock.assert_not_awaited() - yield from mock() + asyncio.run(self.runnable_test()) with self.assertRaises(AssertionError): - mock.assert_not_awaited() + self.mock.assert_not_awaited() diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index 0f0d99f43a7db2..af1ce7ebbae4da 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -2,7 +2,6 @@ # E-mail: fuzzyman AT voidspace DOT org DOT uk # http://www.voidspace.org.uk/python/mock/ -import inspect import unittest from unittest.test.testmock.support import is_instance, X, SomeClass @@ -147,12 +146,6 @@ def test_create_autospec_instance(self): self.assertRaises(TypeError, mock.wibble, 'some', 'args') - def test_inspect_iscoroutine_function(self): - def foo(): pass - - mock = Mock(foo) - self.assertFalse(inspect.iscoroutine(mock)) - if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst new file mode 100644 index 00000000000000..eb6651045c83db --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst @@ -0,0 +1,2 @@ +Added CoroutineMock to support using unittest to mock asyncio coroutines. +Patch by Lisa Roach. From 302ef648b854475e9b68e76d869dde01a9b23cb3 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Thu, 13 Sep 2018 22:19:00 -0700 Subject: [PATCH 10/24] Fixes environment test issue. --- Lib/unittest/test/testmock/testasync.py | 47 ++++++++++++++----------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index b6ae5de7c65884..23867e83531dbd 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -335,8 +335,13 @@ class CoroutineMockAssert(unittest.TestCase): def setUp(self): self.mock = CoroutineMock() + self.old_policy = asyncio.events._event_loop_policy - async def runnable_test(self, *args): + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy + + async def _runnable_test(self, *args): if not args: await self.mock() else: @@ -346,29 +351,29 @@ def test_assert_awaited(self): with self.assertRaises(AssertionError): self.mock.assert_awaited() - asyncio.run(self.runnable_test()) + asyncio.run(self._runnable_test()) self.mock.assert_awaited() def test_assert_awaited_once(self): with self.assertRaises(AssertionError): self.mock.assert_awaited_once() - asyncio.run(self.runnable_test()) + asyncio.run(self._runnable_test()) self.mock.assert_awaited_once() - asyncio.run(self.runnable_test()) + asyncio.run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_awaited_once() def test_assert_awaited_with(self): - asyncio.run(self.runnable_test()) + asyncio.run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_awaited_with('foo') - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) self.mock.assert_awaited_with('foo') - asyncio.run(self.runnable_test('SomethingElse')) + asyncio.run(self._runnable_test('SomethingElse')) with self.assertRaises(AssertionError): self.mock.assert_awaited_with('foo') @@ -376,10 +381,10 @@ def test_assert_awaited_once_with(self): with self.assertRaises(AssertionError): self.mock.assert_awaited_once_with('foo') - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) self.mock.assert_awaited_once_with('foo') - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_awaited_once_with('foo') @@ -387,14 +392,14 @@ def test_assert_any_wait(self): with self.assertRaises(AssertionError): self.mock.assert_any_await('NormalFoo') - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_any_await('NormalFoo') - asyncio.run(self.runnable_test('NormalFoo')) + asyncio.run(self._runnable_test('NormalFoo')) self.mock.assert_any_await('NormalFoo') - asyncio.run(self.runnable_test('SomethingElse')) + asyncio.run(self._runnable_test('SomethingElse')) self.mock.assert_any_await('NormalFoo') def test_assert_has_awaits_no_order(self): @@ -403,18 +408,18 @@ def test_assert_has_awaits_no_order(self): with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - asyncio.run(self.runnable_test('NormalFoo')) + asyncio.run(self._runnable_test('NormalFoo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls) - asyncio.run(self.runnable_test('baz')) + asyncio.run(self._runnable_test('baz')) self.mock.assert_has_awaits(calls) - asyncio.run(self.runnable_test('SomethingElse')) + asyncio.run(self._runnable_test('SomethingElse')) self.mock.assert_has_awaits(calls) def test_assert_has_awaits_ordered(self): @@ -422,23 +427,23 @@ def test_assert_has_awaits_ordered(self): with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self.runnable_test('baz')) + asyncio.run(self._runnable_test('baz')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self.runnable_test('foo')) + asyncio.run(self._runnable_test('foo')) with self.assertRaises(AssertionError): self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self.runnable_test('NormalFoo')) + asyncio.run(self._runnable_test('NormalFoo')) self.mock.assert_has_awaits(calls, any_order=True) - asyncio.run(self.runnable_test('qux')) + asyncio.run(self._runnable_test('qux')) self.mock.assert_has_awaits(calls, any_order=True) def test_assert_not_awaited(self): self.mock.assert_not_awaited() - asyncio.run(self.runnable_test()) + asyncio.run(self._runnable_test()) with self.assertRaises(AssertionError): self.mock.assert_not_awaited() From bf749ac6b84d4e5d4aecb29f23797f19d927a125 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Fri, 14 Sep 2018 00:15:20 -0700 Subject: [PATCH 11/24] Adds argument tests. --- Lib/unittest/test/testmock/testasync.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 23867e83531dbd..30f83d0025bd3e 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -265,6 +265,22 @@ def test_is_child_coroutinemock(self): self.assertTrue(isinstance(mock, MagicMock)) +class CoroutineArguments(unittest.TestCase): + async def compute(x): + yield x + + def test_add_return_value(self): + mock = CoroutineMock(self.compute, return_value=10) + output = mock(5) + self.assertEqual(output, 10) + + def test_add_side_effect(self): + pass + + def test_add_side_effect_with_exception(self): + pass + + class CoroutineMagicMethods(unittest.TestCase): class AsyncContextManager: def __init__(self): From 30b64b53956d7eff6a3e353eff5026d18bf28e9a Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Fri, 14 Sep 2018 00:20:02 -0700 Subject: [PATCH 12/24] Adding the side_effect exception test. --- Lib/unittest/test/testmock/testasync.py | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 30f83d0025bd3e..f9bc802d31631d 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -266,19 +266,30 @@ def test_is_child_coroutinemock(self): class CoroutineArguments(unittest.TestCase): - async def compute(x): - yield x + # I want to add more tests here with more complicate use-cases. + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy def test_add_return_value(self): - mock = CoroutineMock(self.compute, return_value=10) - output = mock(5) + async def addition(self, var): + return var + 1 + + mock = CoroutineMock(addition, return_value=10) + output = asyncio.run(mock(5)) + self.assertEqual(output, 10) - def test_add_side_effect(self): - pass + def test_add_side_effect_exception(self): + async def addition(self, var): + return var + 1 - def test_add_side_effect_with_exception(self): - pass + mock = CoroutineMock(addition, side_effect=Exception('err')) + with self.assertRaises(Exception): + asyncio.run(mock(5)) class CoroutineMagicMethods(unittest.TestCase): From 5edac2a053256898c5ec392f2368268a57ddd863 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 7 May 2019 11:16:14 -0400 Subject: [PATCH 13/24] Changes CoroutineMock to AsyncMock. Removes old-style coroutine references. --- Lib/unittest/mock.py | 364 ++++++++++-------------- Lib/unittest/test/testmock/testasync.py | 298 +++++++------------ Lib/unittest/test/testmock/testmock.py | 4 +- 3 files changed, 254 insertions(+), 412 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index fea43b71e88719..b18d6688c36257 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -13,7 +13,7 @@ 'ANY', 'call', 'create_autospec', - 'CoroutineMock', + 'AsyncMock', 'FILTER_DIR', 'NonCallableMock', 'NonCallableMagicMock', @@ -50,9 +50,9 @@ # Without this, the __class__ properties wouldn't be set correctly _safe_super = super -def _is_coroutine_obj(obj): +def _is_async_obj(obj): if getattr(obj, '__code__', None): - return asyncio.iscoroutinefunction(obj) or asyncio.iscoroutine(obj) + return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) else: return False @@ -366,8 +366,6 @@ def __init__(self, *args, **kwargs): pass -_is_coroutine = asyncio.coroutines._is_coroutine - class NonCallableMock(Base): """A non-callable version of `Mock`""" @@ -376,15 +374,15 @@ def __new__(cls, *args, **kw): # so we can create magic methods on the # class without stomping on other mocks bases = (cls,) - if cls != CoroutineMock: - # Check if spec is a coroutine object or function + if cls != AsyncMock: + # Check if spec is an async object or function sig = inspect.signature(NonCallableMock.__init__) bound_args = sig.bind_partial(cls, *args, **kw).arguments spec_arg = fnmatch.filter(bound_args.keys(), 'spec*') if spec_arg: # what if spec_set is different than spec? - if _is_coroutine_obj(bound_args[spec_arg[0]]): - bases = (CoroutineMockMixin, cls,) + if _is_async_obj(bound_args[spec_arg[0]]): + bases = (AsyncMockMixin, cls,) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = object.__new__(new) return instance @@ -459,11 +457,11 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _eat_self=False): _spec_class = None _spec_signature = None - _spec_coroutines = [] + _spec_asyncs = [] for attr in dir(spec): if asyncio.iscoroutinefunction(getattr(spec, attr, None)): - _spec_coroutines.append(attr) + _spec_asyncs.append(attr) if spec is not None and not _is_list(spec): if isinstance(spec, type): @@ -481,7 +479,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, __dict__['_spec_set'] = spec_set __dict__['_spec_signature'] = _spec_signature __dict__['_mock_methods'] = spec - __dict__['_spec_coroutines'] = _spec_coroutines + __dict__['_spec_asyncs'] = _spec_asyncs def __get_return_value(self): ret = self._mock_return_value @@ -916,13 +914,13 @@ def _get_child_mock(self, **kw): For non-callable mocks the callable variant will be used (rather than any custom subclass).""" _new_name = kw.get("_new_name") - if _new_name in self.__dict__['_spec_coroutines']: - return CoroutineMock(**kw) + if _new_name in self.__dict__['_spec_asyncs']: + return AsyncMock(**kw) _type = type(self) - if issubclass(_type, MagicMock) and _new_name in async_magic_coroutines: - klass = CoroutineMock - if issubclass(_type, CoroutineMockMixin): + if issubclass(_type, MagicMock) and _new_name in _async_method_magics: + klass = AsyncMock + if issubclass(_type, AsyncMockMixin): klass = MagicMock if not issubclass(_type, CallableMixin): if issubclass(_type, NonCallableMagicMock): @@ -956,99 +954,6 @@ def _try_iter(obj): return obj -class _AwaitEvent: - def __init__(self, mock): - self._mock = mock - self._condition = None - - @asyncio.coroutine - def wait(self, skip=0): - """ - Wait for await. - - :param skip: How many awaits will be skipped. - As a result, the mock should be awaited at least - ``skip + 1`` times. - """ - def predicate(mock): - return mock.await_count > skip - - return (yield from self.wait_for(predicate)) - - @asyncio.coroutine - def wait_next(self, skip=0): - """ - Wait for the next await. - - Unlike :meth:`wait` that counts any await, mock has to be awaited once - more, disregarding to the current - :attr:`asynctest.CoroutineMock.await_count`. - - :param skip: How many awaits will be skipped. - As a result, the mock should be awaited at least - ``skip + 1`` more times. - """ - await_count = self._mock.await_count - - def predicate(mock): - return mock.await_count > await_count + skip - - return (yield from self.wait_for(predicate)) - - @asyncio.coroutine - def wait_for(self, predicate): - """ - Wait for a given predicate to become True. - - :param predicate: A callable that receives mock which result - will be interpreted as a boolean value. - The final predicate value is the return value. - """ - condition = self._get_condition() - - try: - yield from condition.acquire() - - def _predicate(): - return predicate(self._mock) - - return (yield from condition.wait_for(_predicate)) - finally: - condition.release() - - @asyncio.coroutine - def _notify(self): - condition = self._get_condition() - - try: - yield from condition.acquire() - condition.notify_all() - finally: - condition.release() - - def _get_condition(self): - """ - Creation of condition is delayed, to minimize the change of using the - wrong loop. - - A user may create a mock with _AwaitEvent before selecting the - execution loop. Requiring a user to delay creation is error-prone and - inflexible. Instead, condition is created when user actually starts to - use the mock. - """ - # No synchronization is needed: - # - asyncio is thread unsafe - # - there are no awaits here, method will be executed without - # switching asyncio context. - if self._condition is None: - self._condition = asyncio.Condition() - - return self._condition - - def __bool__(self): - return self._mock.await_count != 0 - - class CallableMixin(Base): def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, @@ -1201,10 +1106,6 @@ class or instance) that acts as the specification for the mock object. If """ -def _raise(exception): - raise exception - - def _dot_lookup(thing, comp, import_path): try: return getattr(thing, comp) @@ -1402,8 +1303,8 @@ def __enter__(self): if isinstance(original, type): # If we're patching out a class and there is a spec inherit = True - if spec is None and _is_coroutine_obj(original): - Klass = CoroutineMock + if spec is None and _is_async_obj(original): + Klass = AsyncMock else: Klass = MagicMock _kwargs = {} @@ -1417,8 +1318,8 @@ def __enter__(self): not_callable = '__call__' not in this_spec else: not_callable = not callable(this_spec) - if _is_coroutine_obj(this_spec): - Klass = CoroutineMock + if _is_async_obj(this_spec): + Klass = AsyncMock elif not_callable: Klass = NonCallableMagicMock @@ -1876,13 +1777,10 @@ def method(self, *args, **kw): ' '.join([magic_methods, numerics, inplace, right]).split() } -async_magic_coroutines = ("__aenter__", "__aexit__", "__anext__") -_async_magics = async_magic_coroutines + ("__aiter__", ) - -async_magic_coroutines = set(async_magic_coroutines) -_async_magics = set(_async_magics) - - +# Magic methods used for async `with` statements +_async_method_magics = {"__aenter__", "__aexit__", "__anext__"} +# `__aiter__` is a plain function but used with async calls +_async_magics = _async_method_magics | {"__aiter__"} _all_magics = _magics | _non_defaults @@ -1914,22 +1812,6 @@ def method(self, *args, **kw): '__index__': 1, } -class AsyncIterator: - """ - Wraps an iterator in an asynchronous iterator. - """ - def __init__(self, iterator): - self.iterator = iterator - - def __aiter__(self): - return self - - async def __anext__(self): - try: - return next(self.iterator) - except StopIteration: - pass - raise StopAsyncIteration def _get_eq(self): def __eq__(other): @@ -1960,19 +1842,12 @@ def __iter__(): return iter(ret_val) return __iter__ -def _get_async_iter(mock): +def _get_async_iter(self): def __aiter__(): - return_value = mock.__aiter__._mock_return_value - if return_value is DEFAULT: - iterator = iter([]) - else: - iterator = iter(return_value) - - return AsyncIterator(iterator) - - if asyncio.iscoroutinefunction(mock.__aiter__): - return asyncio.coroutine(__aiter__) - + ret_val = self.__aiter__._mock_return_value + if ret_val is DEFAULT: + return AsyncIterator([]) + return AsyncIterator(ret_val) return __aiter__ _side_effect_methods = { @@ -2094,7 +1969,7 @@ def __get__(self, obj, _type=None): return self.create_mock() -class AsyncMagicMixin(object): +class AsyncMagicMixin: def __init__(self, *args, **kw): self._mock_set_async_magics() # make magic work for kwargs in init _safe_super(AsyncMagicMixin, self).__init__(*args, **kw) @@ -2120,7 +1995,7 @@ def _mock_set_async_magics(self): setattr(_type, entry, MagicProxy(entry, self)) -class CoroutineMockMixin(Base): +class AsyncMockMixin(Base): awaited = _delegating_property('awaited') await_count = _delegating_property('await_count') await_args = _delegating_property('await_args') @@ -2128,48 +2003,46 @@ class CoroutineMockMixin(Base): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - - # asyncio.iscoroutinefunction() checks this property to say if an - # object is a coroutine + # asyncio.iscoroutinefunction() checks _is_coroutine property to say if an + # object is a coroutine. Without this check it looks to see if it is a + # function/method, which in this case it is not (since it is an + # AsyncMock). # It is set through __dict__ because when spec_set is True, this # attribute is likely undefined. - self.__dict__['_is_coroutine'] = _is_coroutine + self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine self.__dict__['_mock_awaited'] = _AwaitEvent(self) self.__dict__['_mock_await_count'] = 0 self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = _CallList() code_mock = NonCallableMock(spec_set=CodeType) - code_mock.co_flags = 129 + code_mock.co_flags = inspect.CO_COROUTINE self.__dict__['__code__'] = code_mock - - def _mock_call(_mock_self, *args, **kwargs): + async def _mock_call(_mock_self, *args, **kwargs): + self = _mock_self try: result = super()._mock_call(*args, **kwargs) - _call = _mock_self.call_args - - @asyncio.coroutine - def proxy(): - try: - if asyncio.iscoroutine(result): - return (yield from result) - else: - return result - finally: - _mock_self.await_count += 1 - _mock_self.await_args = _call - _mock_self.await_args_list.append(_call) - yield from _mock_self.awaited._notify() - - return proxy() - except StopIteration as e: - side_effect = _mock_self.side_effect + except (BaseException, StopIteration) as e: + side_effect = self.side_effect if side_effect is not None and not callable(side_effect): raise + return _raise(e) + + _call = self.call_args + + async def proxy(): + try: + if inspect.isawaitable(result): + return await result + else: + return result + finally: + self.await_count += 1 + self.await_args = _call + self.await_args_list.append(_call) + await self.awaited._notify() - return asyncio.coroutine(_raise)(e) - except BaseException as e: - return asyncio.coroutine(_raise)(e) + return await proxy() def assert_awaited(_mock_self): """ @@ -2287,49 +2160,49 @@ def reset_mock(self, *args, **kwargs): See :func:`.Mock.reset_mock()` """ super().reset_mock(*args, **kwargs) - self.awaited = _AwaitEvent(self) self.await_count = 0 self.await_args = None self.await_args_list = _CallList() -class CoroutineMock(CoroutineMockMixin, AsyncMagicMixin, Mock): +class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): """ Enhance :class:`Mock` with features allowing to mock - a coroutine function. + a async function. - The :class:`CoroutineMock` object will behave so the object is - recognized as coroutine function, and the result of a call as a coroutine: + The :class:`AsyncMock` object will behave so the object is + recognized as async function, and the result of a call as a async: - >>> mock = CoroutineMock() + >>> mock = AsyncMock() >>> asyncio.iscoroutinefunction(mock) True - >>> asyncio.iscoroutine(mock()) + >>> inspect.isawaitable(mock()) True - The result of ``mock()`` is a coroutine which will have the outcome of - ``side_effect`` or ``return_value``: + The result of ``mock()`` is an async function which will have the outcome + of ``side_effect`` or ``return_value``: - - if ``side_effect`` is a function, the coroutine will return the result - of that function, - - if ``side_effect`` is an exception, the coroutine will raise the + - if ``side_effect`` is a function, the async function will return the + result of that function, + - if ``side_effect`` is an exception, the async function will raise the exception, - - if ``side_effect`` is an iterable, the coroutine will return the next - value of the iterable, however, if the sequence of result is exhausted, - ``StopIteration`` is raised immediately, - - if ``side_effect`` is not defined, the coroutine will return the value - defined by ``return_value``, hence, by default, the coroutine returns - a new :class:`CoroutineMock` object. - - If the outcome of ``side_effect`` or ``return_value`` is a coroutine, the - mock coroutine obtained when the mock object is called will be this - coroutine itself (and not a coroutine returning a coroutine). + - if ``side_effect`` is an iterable, the async function will return the + next value of the iterable, however, if the sequence of result is + exhausted, ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the async function will return the + value defined by ``return_value``, hence, by default, the async function + returns a new :class:`AsyncMock` object. + + If the outcome of ``side_effect`` or ``return_value`` is an async function, the + mock async function obtained when the mock object is called will be this + async function itself (and not an async function returning an async + function). The test author can also specify a wrapped object with ``wraps``. In this case, the :class:`Mock` object behavior is the same as with an :class:`.Mock` object: the wrapped object may have methods - defined as coroutine functions. + defined as async function functions. """ @@ -2564,9 +2437,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, is_type = isinstance(spec, type) if getattr(spec, '__code__', None): - is_coroutine_func = asyncio.iscoroutinefunction(spec) + is_async_func = asyncio.iscoroutinefunction(spec) else: - is_coroutine_func = False + is_async_func = False _kwargs = {'spec': spec} if spec_set: _kwargs = {'spec_set': spec} @@ -2583,11 +2456,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # descriptors don't have a spec # because we don't know what type they return _kwargs = {} - elif is_coroutine_func: + elif is_async_func: if instance: raise RuntimeError("Instance can not be True when create_autospec " - "is mocking a coroutine function") - Klass = CoroutineMock + "is mocking an async function") + Klass = AsyncMock elif not _callable(spec): Klass = NonCallableMagicMock elif is_type and instance and not _instance_callable(spec): @@ -2604,17 +2477,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, name=_name, **_kwargs) if isinstance(spec, FunctionTypes): + wrapped_mock = mock # should only happen at the top level because we don't # recurse for functions mock = _set_signature(mock, spec) - if is_coroutine_func: - # Can't wrap the mock with asyncio.coroutine because it doesn't - # detect a CoroWrapper as an awaitable in debug mode. - # It is safe to do so because the mock object wrapped by - # _set_signature returns the result of the CoroutineMock itself, - # which is a Coroutine (as defined in CoroutineMock._mock_call) + if is_async_func: mock._is_coroutine = _is_coroutine - mock.awaited = _AwaitEvent(mock) mock.await_count = 0 mock.await_args = None mock.await_args_list = _CallList() @@ -2673,7 +2541,7 @@ def f(*args, **kwargs): skipfirst = _must_skip(spec, entry, is_type) kwargs['_eat_self'] = skipfirst if asyncio.iscoroutinefunction(original): - child_klass = CoroutineMock + child_klass = AsyncMock else: child_klass = MagicMock new = child_klass(parent=parent, name=entry, _new_name=entry, @@ -2883,3 +2751,61 @@ def seal(mock): continue if m._mock_new_parent is mock: seal(m) + + +async def _raise(exception): + raise exception + + +class AsyncIterator: + """ + Wraps an iterator in an asynchronous iterator. + """ + def __init__(self, iterator): + self.iterator = iterator + code_mock = NonCallableMock(spec_set=CodeType) + code_mock.co_flags = inspect.CO_ITERATOR_COROUTINE + self.__dict__['__code__'] = code_mock + + def __aiter__(self): + return self + + # cannot use async before 3.7 + async def __anext__(self): + try: + return next(self.iterator) + except StopIteration: + pass + raise StopAsyncIteration + + +class _AwaitEvent: + def __init__(self, mock): + self._mock = mock + self._condition = None + + async def _notify(self): + condition = self._get_condition() + try: + await condition.acquire() + condition.notify_all() + finally: + condition.release() + + def _get_condition(self): + """ + Creation of condition is delayed, to minimize the chance of using the + wrong loop. + A user may create a mock with _AwaitEvent before selecting the + execution loop. Requiring a user to delay creation is error-prone and + inflexible. Instead, condition is created when user actually starts to + use the mock. + """ + # No synchronization is needed: + # - asyncio is thread unsafe + # - there are no awaits here, method will be executed without + # switching asyncio context. + if self._condition is None: + self._condition = asyncio.Condition() + + return self._condition diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index f9bc802d31631d..2821b5aa252692 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -1,23 +1,19 @@ import asyncio import inspect -import sys import unittest -from unittest.mock import call, CoroutineMock, patch, MagicMock +from unittest.mock import call, AsyncMock, patch, MagicMock - -class AsyncClass(object): - def __init__(self, a): - pass - async def coroutine_method(self): +# TODO: lisa move 3.7 specific syntax to its own file +class AsyncClass: + def __init__(self): pass - @asyncio.coroutine - def decorated_cr_method(self): + async def async_method(self): pass def normal_method(self): pass -async def coroutine_func(): +async def async_func(): pass def normal_func(): @@ -32,132 +28,91 @@ def a(self): normal_foo_name = f'{__name__}.NormalClass' -def run_coroutine(coroutine): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - return loop.run_until_complete(coroutine) - finally: - loop.close() - - -class CoroutinePatchDecoratorTest(unittest.TestCase): +class AsyncPatchDecoratorTest(unittest.TestCase): def test_is_coroutine_function_patch(self): - @patch.object(AsyncClass, 'coroutine_method') + # @patch.object(AsyncClass, 'normal_method') + # def test_normal_method(mock_method): + # import types + # self.assertTrue(isinstance(mock_method, types.MethodType)) + @patch.object(AsyncClass, 'async_method') def test_async(mock_method): self.assertTrue(asyncio.iscoroutinefunction(mock_method)) - - @patch.object(AsyncClass, 'decorated_cr_method') - def test_async_decorator(mock_g): - self.assertTrue(asyncio.iscoroutinefunction(mock_g)) - + # test_normal_method() test_async() - test_async_decorator() - def test_is_coroutine_patch(self): - @patch.object(AsyncClass, 'coroutine_method') + def test_is_async_patch(self): + @patch.object(AsyncClass, 'async_method') def test_async(mock_method): - self.assertTrue(asyncio.iscoroutine(mock_method())) + self.assertTrue(inspect.isawaitable(mock_method())) - @patch.object(AsyncClass, 'decorated_cr_method') - def test_async_decorator(mock_method): - self.assertTrue(asyncio.iscoroutine(mock_method())) - - @patch(f'{async_foo_name}.coroutine_method') + @patch(f'{async_foo_name}.async_method') def test_no_parent_attribute(mock_method): - self.assertTrue(asyncio.iscoroutine(mock_method())) + self.assertTrue(inspect.isawaitable(mock_method())) test_async() - test_async_decorator() test_no_parent_attribute() - def test_is_coroutinemock_patch(self): - @patch.object(AsyncClass, 'coroutine_method') + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method') def test_async(mock_method): - self.assertTrue(isinstance(mock_method, CoroutineMock)) - - @patch.object(AsyncClass, 'decorated_cr_method') - def test_async_decorator(mock_method): - self.assertTrue(isinstance(mock_method, CoroutineMock)) + self.assertTrue(isinstance(mock_method, AsyncMock)) test_async() - test_async_decorator() -class CoroutinePatchCMTest(unittest.TestCase): - def test_is_coroutine_function_cm(self): +class AsyncPatchCMTest(unittest.TestCase): + def test_is_async_function_cm(self): def test_async(): - with patch.object(AsyncClass, 'coroutine_method') as mock_method: - self.assertTrue(asyncio.iscoroutinefunction(mock_method)) - - def test_async_decorator(): - with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: + with patch.object(AsyncClass, 'async_method') as mock_method: self.assertTrue(asyncio.iscoroutinefunction(mock_method)) test_async() - test_async_decorator() - def test_is_coroutine_cm(self): + def test_is_async_cm(self): def test_async(): - with patch.object(AsyncClass, 'coroutine_method') as mock_method: - self.assertTrue(asyncio.iscoroutine(mock_method())) + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertTrue(inspect.isawaitable(mock_method())) - def test_async_decorator(): - with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: - self.assertTrue(asyncio.iscoroutine(mock_method())) test_async() - test_async_decorator() - def test_is_coroutinemock_cm(self): + def test_is_AsyncMock_cm(self): def test_async(): - with patch.object(AsyncClass, 'coroutine_method') as mock_method: - self.assertTrue(isinstance(mock_method, CoroutineMock)) - - def test_async_decorator(): - with patch.object(AsyncClass, 'decorated_cr_method') as mock_method: - self.assertTrue(isinstance(mock_method, CoroutineMock)) + with patch.object(AsyncClass, 'async_method') as mock_method: + self.assertTrue(isinstance(mock_method, AsyncMock)) test_async() - test_async_decorator() -class CoroutineMockTest(unittest.TestCase): +class AsyncMockTest(unittest.TestCase): def test_iscoroutinefunction_default(self): - mock = CoroutineMock() + mock = AsyncMock() self.assertTrue(asyncio.iscoroutinefunction(mock)) def test_iscoroutinefunction_function(self): async def foo(): pass - mock = CoroutineMock(foo) + mock = AsyncMock(foo) self.assertTrue(asyncio.iscoroutinefunction(mock)) self.assertTrue(inspect.iscoroutinefunction(mock)) - def test_iscoroutine(self): - mock = CoroutineMock() - self.assertTrue(asyncio.iscoroutine(mock())) + def test_isawaitable(self): + mock = AsyncMock() + self.assertTrue(inspect.isawaitable(mock())) self.assertIn('assert_awaited', dir(mock)) def test_iscoroutinefunction_normal_function(self): def foo(): pass - mock = CoroutineMock(foo) + mock = AsyncMock(foo) self.assertTrue(asyncio.iscoroutinefunction(mock)) self.assertTrue(inspect.iscoroutinefunction(mock)) -class CoroutineAutospecTest(unittest.TestCase): - def test_is_coroutinemock_patch(self): +class AsyncAutospecTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): @patch(async_foo_name, autospec=True) def test_async(mock_method): self.assertTrue(isinstance( - mock_method.coroutine_method, - CoroutineMock)) - self.assertTrue(isinstance(mock_method, MagicMock)) - - @patch(async_foo_name, autospec=True) - def test_async_decorator(mock_method): - self.assertTrue(isinstance( - mock_method.decorated_cr_method, - CoroutineMock)) + mock_method.async_method, + AsyncMock)) self.assertTrue(isinstance(mock_method, MagicMock)) @patch(async_foo_name, autospec=True) @@ -167,106 +122,103 @@ def test_normal_method(mock_method): MagicMock)) test_async() - test_async_decorator() test_normal_method() -class CoroutineSpecTest(unittest.TestCase): - def test_spec_as_coroutine_positional_magicmock(self): - mock = MagicMock(coroutine_func) +class AsyncSpecTest(unittest.TestCase): + def test_spec_as_async_positional_magicmock(self): + mock = MagicMock(async_func) self.assertIsInstance(mock, MagicMock) - self.assertTrue(asyncio.iscoroutine(mock())) + self.assertTrue(inspect.isawaitable(mock())) - def test_spec_as_coroutine_kw_magicmock(self): - mock = MagicMock(spec=coroutine_func) + def test_spec_as_async_kw_magicmock(self): + mock = MagicMock(spec=async_func) self.assertIsInstance(mock, MagicMock) - self.assertTrue(asyncio.iscoroutine(mock())) - - def test_spec_as_coroutine_kw_coroutinemock(self): - mock = CoroutineMock(spec=coroutine_func) - self.assertIsInstance(mock, CoroutineMock) - self.assertTrue(asyncio.iscoroutine(mock())) - - def test_spec_as_coroutine_positional_coroutinemock(self): - mock = CoroutineMock(coroutine_func) - self.assertIsInstance(mock, CoroutineMock) - self.assertTrue(asyncio.iscoroutine(mock())) - - def test_spec_as_normal_kw_coroutinemock(self): - mock = CoroutineMock(spec=normal_func) - self.assertIsInstance(mock, CoroutineMock) - self.assertTrue(asyncio.iscoroutine(mock())) - - def test_spec_as_normal_positional_coroutinemock(self): - mock = CoroutineMock(normal_func) - self.assertIsInstance(mock, CoroutineMock) - self.assertTrue(asyncio.iscoroutine(mock())) - - def test_spec_coroutine_mock(self): - @patch.object(AsyncClass, 'coroutine_method', spec=True) + self.assertTrue(inspect.isawaitable(mock())) + + def test_spec_as_async_kw_AsyncMock(self): + mock = AsyncMock(spec=async_func) + self.assertIsInstance(mock, AsyncMock) + self.assertTrue(inspect.isawaitable(mock())) + + def test_spec_as_async_positional_AsyncMock(self): + mock = AsyncMock(async_func) + self.assertIsInstance(mock, AsyncMock) + self.assertTrue(inspect.isawaitable(mock())) + + def test_spec_as_normal_kw_AsyncMock(self): + mock = AsyncMock(spec=normal_func) + self.assertIsInstance(mock, AsyncMock) + self.assertTrue(inspect.isawaitable(mock())) + + def test_spec_as_normal_positional_AsyncMock(self): + mock = AsyncMock(normal_func) + self.assertIsInstance(mock, AsyncMock) + self.assertTrue(inspect.isawaitable(mock())) + + def test_spec_async_mock(self): + @patch.object(AsyncClass, 'async_method', spec=True) def test_async(mock_method): - self.assertTrue(isinstance(mock_method, CoroutineMock)) + self.assertTrue(isinstance(mock_method, AsyncMock)) test_async() - def test_spec_parent_not_coroutine_attribute_is(self): + def test_spec_parent_not_async_attribute_is(self): @patch(async_foo_name, spec=True) def test_async(mock_method): self.assertTrue(isinstance(mock_method, MagicMock)) - self.assertTrue(isinstance(mock_method.coroutine_method, - CoroutineMock)) + self.assertTrue(isinstance(mock_method.async_method, + AsyncMock)) test_async() - def test_target_coroutine_spec_not(self): - @patch.object(AsyncClass, 'coroutine_method', spec=NormalClass.a) + def test_target_async_spec_not(self): + @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) def test_async_attribute(mock_method): self.assertTrue(isinstance(mock_method, MagicMock)) - self.assertFalse(inspect.iscoroutine(mock_method)) - self.assertFalse(asyncio.iscoroutine(mock_method)) + self.assertFalse(inspect.iscoroutine(mock_method)) # TODO: lisa? + self.assertFalse(inspect.isawaitable(mock_method)) test_async_attribute() - def test_target_not_coroutine_spec_is(self): - @patch.object(NormalClass, 'a', spec=coroutine_func) - def test_attribute_not_coroutine_spec_is(mock_coroutine_func): - self.assertTrue(isinstance(mock_coroutine_func, CoroutineMock)) - test_attribute_not_coroutine_spec_is() + def test_target_not_async_spec_is(self): + @patch.object(NormalClass, 'a', spec=async_func) + def test_attribute_not_async_spec_is(mock_async_func): + self.assertTrue(isinstance(mock_async_func, AsyncMock)) + test_attribute_not_async_spec_is() - def test_spec_coroutine_attributes(self): + def test_spec_async_attributes(self): @patch(normal_foo_name, spec=AsyncClass) - def test_coroutine_attributes_coroutines(MockNormalClass): - self.assertTrue(isinstance(MockNormalClass.coroutine_method, - CoroutineMock)) + def test_async_attributes_coroutines(MockNormalClass): + self.assertTrue(isinstance(MockNormalClass.async_method, + AsyncMock)) self.assertTrue(isinstance(MockNormalClass, MagicMock)) - test_coroutine_attributes_coroutines() + test_async_attributes_coroutines() -class CoroutineSpecSetTest(unittest.TestCase): - def test_is_coroutinemock_patch(self): - @patch.object(AsyncClass, 'coroutine_method', spec_set=True) - def test_async(coroutine_method): - self.assertTrue(isinstance(coroutine_method, CoroutineMock)) +class AsyncSpecSetTest(unittest.TestCase): + def test_is_AsyncMock_patch(self): + @patch.object(AsyncClass, 'async_method', spec_set=True) + def test_async(async_method): + self.assertTrue(isinstance(async_method, AsyncMock)) - def test_is_coroutine_coroutinemock(self): - mock = CoroutineMock(spec_set=AsyncClass.coroutine_method) + def test_is_async_AsyncMock(self): + mock = AsyncMock(spec_set=AsyncClass.async_method) self.assertTrue(asyncio.iscoroutinefunction(mock)) - self.assertTrue(isinstance(mock, CoroutineMock)) + self.assertTrue(isinstance(mock, AsyncMock)) - def test_is_child_coroutinemock(self): + def test_is_child_AsyncMock(self): mock = MagicMock(spec_set=AsyncClass) - self.assertTrue(asyncio.iscoroutinefunction(mock.coroutine_method)) - self.assertTrue(asyncio.iscoroutinefunction(mock.decorated_cr_method)) + self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) - self.assertTrue(isinstance(mock.coroutine_method, CoroutineMock)) - self.assertTrue(isinstance(mock.decorated_cr_method, CoroutineMock)) + self.assertTrue(isinstance(mock.async_method, AsyncMock)) self.assertTrue(isinstance(mock.normal_method, MagicMock)) self.assertTrue(isinstance(mock, MagicMock)) -class CoroutineArguments(unittest.TestCase): - # I want to add more tests here with more complicate use-cases. +class AsyncArguments(unittest.TestCase): + # I want to add more tests here with more complicated use-cases. def setUp(self): self.old_policy = asyncio.events._event_loop_policy @@ -278,7 +230,7 @@ def test_add_return_value(self): async def addition(self, var): return var + 1 - mock = CoroutineMock(addition, return_value=10) + mock = AsyncMock(addition, return_value=10) output = asyncio.run(mock(5)) self.assertEqual(output, 10) @@ -286,13 +238,12 @@ async def addition(self, var): def test_add_side_effect_exception(self): async def addition(self, var): return var + 1 - - mock = CoroutineMock(addition, side_effect=Exception('err')) + mock = AsyncMock(addition, side_effect=Exception('err')) with self.assertRaises(Exception): asyncio.run(mock(5)) -class CoroutineMagicMethods(unittest.TestCase): +class AsyncMagicMethods(unittest.TestCase): class AsyncContextManager: def __init__(self): self.entered = False @@ -322,46 +273,11 @@ async def __anext__(self): raise StopAsyncIteration - # Before 3.7 __aiter__ was a coroutine - class AsyncItertorDeprecated(AsyncIterator): - async def __aiter__(self): - return super().__aiter__() - - def test_mock_magic_methods_are_coroutine_mocks(self): - mock_instance = CoroutineMock(spec=self.AsyncContextManager()) - self.assertIsInstance(mock_instance.__aenter__, - CoroutineMock) - self.assertIsInstance(mock_instance.__aexit__, - CoroutineMock) - - def test_mock_aiter_and_anext(self): - if sys.version_info < (3, 7): - instance = self.AsyncItertorDeprecated() - else: - instance = self.AsyncIterator() - mock_instance = CoroutineMock(instance) - - self.assertEqual(asyncio.iscoroutine(instance.__aiter__), - asyncio.iscoroutine(mock_instance.__aiter__)) - self.assertEqual(asyncio.iscoroutine(instance.__anext__), - asyncio.iscoroutine(mock_instance.__anext__)) - if sys.version_info < (3, 7): - iterator = instance.__aiter__() - iterator = run_coroutine(iterator) - - mock_iterator = mock_instance.__aiter__() - mock_iterator = run_coroutine(mock_iterator) - - self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), - asyncio.iscoroutine(mock_iterator.__aiter__)) - self.assertEqual(asyncio.iscoroutine(iterator.__anext__), - asyncio.iscoroutine(mock_iterator.__anext__)) - -class CoroutineMockAssert(unittest.TestCase): +class AsyncMockAssert(unittest.TestCase): def setUp(self): - self.mock = CoroutineMock() + self.mock = AsyncMock() self.old_policy = asyncio.events._event_loop_policy def tearDown(self): diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 75f174b8aa3928..5aceca356b98d1 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -8,7 +8,7 @@ from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, CoroutineMock, _CallList, + NonCallableMagicMock, AsyncMock, _CallList, create_autospec ) @@ -1378,7 +1378,7 @@ def test_mock_add_spec_magic_methods(self): def test_adding_child_mock(self): for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock, - CoroutineMock): + AsyncMock): mock = Klass() mock.foo = Mock() From 24920a638058a0be6993900d750590692ab404eb Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 7 May 2019 11:42:58 -0400 Subject: [PATCH 14/24] Changes fnmatch to list comp. --- Lib/unittest/mock.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index b18d6688c36257..ccf1ad7713f3a3 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -30,7 +30,6 @@ import pprint import sys import builtins -import fnmatch from types import ModuleType, CodeType from functools import wraps, partial @@ -366,6 +365,7 @@ def __init__(self, *args, **kwargs): pass + class NonCallableMock(Base): """A non-callable version of `Mock`""" @@ -378,7 +378,10 @@ def __new__(cls, *args, **kw): # Check if spec is an async object or function sig = inspect.signature(NonCallableMock.__init__) bound_args = sig.bind_partial(cls, *args, **kw).arguments - spec_arg = fnmatch.filter(bound_args.keys(), 'spec*') + spec_arg = [ + arg for arg in bound_args.keys() + if arg.startswith('spec') + ] if spec_arg: # what if spec_set is different than spec? if _is_async_obj(bound_args[spec_arg[0]]): @@ -387,6 +390,7 @@ def __new__(cls, *args, **kw): instance = object.__new__(new) return instance + def __init__( self, spec=None, wraps=None, name=None, spec_set=None, parent=None, _spec_state=None, _new_name='', _new_parent=None, @@ -431,6 +435,7 @@ def __init__( _spec_state ) + def attach_mock(self, mock, attribute): """ Attach a mock as an attribute of this one, replacing its name and From c0a88a90416225294f3913e03a64acfdf00fadf7 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 7 May 2019 14:47:27 -0400 Subject: [PATCH 15/24] Updates news with AsyncMock name change. --- Lib/unittest/test/testmock/testasync.py | 6 ------ .../next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 2821b5aa252692..47a2f475b02ae7 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -4,7 +4,6 @@ from unittest.mock import call, AsyncMock, patch, MagicMock -# TODO: lisa move 3.7 specific syntax to its own file class AsyncClass: def __init__(self): pass @@ -30,14 +29,9 @@ def a(self): class AsyncPatchDecoratorTest(unittest.TestCase): def test_is_coroutine_function_patch(self): - # @patch.object(AsyncClass, 'normal_method') - # def test_normal_method(mock_method): - # import types - # self.assertTrue(isinstance(mock_method, types.MethodType)) @patch.object(AsyncClass, 'async_method') def test_async(mock_method): self.assertTrue(asyncio.iscoroutinefunction(mock_method)) - # test_normal_method() test_async() def test_is_async_patch(self): diff --git a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst index eb6651045c83db..8f74adbfafa200 100644 --- a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst +++ b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst @@ -1,2 +1,3 @@ -Added CoroutineMock to support using unittest to mock asyncio coroutines. + +Addedi AsyncMock to support using unittest to mock asyncio coroutines. Patch by Lisa Roach. From f9bee6ebd1d23bf0d47475af4141892bec53a5b9 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Tue, 7 May 2019 14:50:49 -0400 Subject: [PATCH 16/24] Removes extraneous comments. --- Lib/unittest/mock.py | 1 - Lib/unittest/test/testmock/testasync.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 074228f96d9472..633950874b6b2c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -26,7 +26,6 @@ __version__ = '1.0' import asyncio -import io import inspect import pprint import sys diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 47a2f475b02ae7..4ae06997452eec 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -170,7 +170,7 @@ def test_target_async_spec_not(self): @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) def test_async_attribute(mock_method): self.assertTrue(isinstance(mock_method, MagicMock)) - self.assertFalse(inspect.iscoroutine(mock_method)) # TODO: lisa? + self.assertFalse(inspect.iscoroutine(mock_method)) self.assertFalse(inspect.isawaitable(mock_method)) test_async_attribute() @@ -212,7 +212,6 @@ def test_is_child_AsyncMock(self): class AsyncArguments(unittest.TestCase): - # I want to add more tests here with more complicated use-cases. def setUp(self): self.old_policy = asyncio.events._event_loop_policy From 81ad0d1a7bf681b01db85899b2c807385db1099c Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 8 May 2019 15:57:58 -0400 Subject: [PATCH 17/24] Fixes RunTime warnings and missing io import. --- Lib/unittest/mock.py | 3 +- Lib/unittest/test/testmock/testasync.py | 68 +++++++++++++++++++++---- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 633950874b6b2c..c75dd915d37b9c 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -26,6 +26,7 @@ __version__ = '1.0' import asyncio +import io import inspect import pprint import sys @@ -2024,7 +2025,7 @@ async def _mock_call(_mock_self, *args, **kwargs): side_effect = self.side_effect if side_effect is not None and not callable(side_effect): raise - return _raise(e) + return await _raise(e) _call = self.call_args diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 4ae06997452eec..ceb0b2510b07be 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -28,6 +28,14 @@ def a(self): class AsyncPatchDecoratorTest(unittest.TestCase): + def setUp(self): + # Prevents altering the execution environment + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy + def test_is_coroutine_function_patch(self): @patch.object(AsyncClass, 'async_method') def test_async(mock_method): @@ -37,11 +45,15 @@ def test_async(mock_method): def test_is_async_patch(self): @patch.object(AsyncClass, 'async_method') def test_async(mock_method): - self.assertTrue(inspect.isawaitable(mock_method())) + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) @patch(f'{async_foo_name}.async_method') def test_no_parent_attribute(mock_method): - self.assertTrue(inspect.isawaitable(mock_method())) + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) test_async() test_no_parent_attribute() @@ -55,6 +67,13 @@ def test_async(mock_method): class AsyncPatchCMTest(unittest.TestCase): + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy + def test_is_async_function_cm(self): def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: @@ -65,7 +84,9 @@ def test_async(): def test_is_async_cm(self): def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: - self.assertTrue(inspect.isawaitable(mock_method())) + m = mock_method() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) test_async() @@ -78,6 +99,13 @@ def test_async(): class AsyncMockTest(unittest.TestCase): + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy + def test_iscoroutinefunction_default(self): mock = AsyncMock() self.assertTrue(asyncio.iscoroutinefunction(mock)) @@ -90,7 +118,9 @@ async def foo(): pass def test_isawaitable(self): mock = AsyncMock() - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) self.assertIn('assert_awaited', dir(mock)) def test_iscoroutinefunction_normal_function(self): @@ -120,35 +150,53 @@ def test_normal_method(mock_method): class AsyncSpecTest(unittest.TestCase): + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy def test_spec_as_async_positional_magicmock(self): mock = MagicMock(async_func) self.assertIsInstance(mock, MagicMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_as_async_kw_magicmock(self): mock = MagicMock(spec=async_func) self.assertIsInstance(mock, MagicMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_as_async_kw_AsyncMock(self): mock = AsyncMock(spec=async_func) self.assertIsInstance(mock, AsyncMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_as_async_positional_AsyncMock(self): mock = AsyncMock(async_func) self.assertIsInstance(mock, AsyncMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_as_normal_kw_AsyncMock(self): mock = AsyncMock(spec=normal_func) self.assertIsInstance(mock, AsyncMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_as_normal_positional_AsyncMock(self): mock = AsyncMock(normal_func) self.assertIsInstance(mock, AsyncMock) - self.assertTrue(inspect.isawaitable(mock())) + m = mock() + self.assertTrue(inspect.isawaitable(m)) + asyncio.run(m) def test_spec_async_mock(self): @patch.object(AsyncClass, 'async_method', spec=True) From c2601049bea2f8335fe074a7ee76690fa081f2b6 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Wed, 8 May 2019 16:48:41 -0400 Subject: [PATCH 18/24] Changes check to use issubclass instead of !=. --- Lib/unittest/mock.py | 2 +- .../next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index c75dd915d37b9c..f1687ae8d559df 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -364,7 +364,7 @@ def __new__(cls, *args, **kw): # so we can create magic methods on the # class without stomping on other mocks bases = (cls,) - if cls != AsyncMock: + if not issubclass(cls, AsyncMock): # Check if spec is an async object or function sig = inspect.signature(NonCallableMock.__init__) bound_args = sig.bind_partial(cls, *args, **kw).arguments diff --git a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst index 8f74adbfafa200..ede9f7253d58ea 100644 --- a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst +++ b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst @@ -1,3 +1,3 @@ -Addedi AsyncMock to support using unittest to mock asyncio coroutines. +Added AsyncMock to support using unittest to mock asyncio coroutines. Patch by Lisa Roach. From ae13db16c2469ef7471e0087911f6bd796d3d965 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Mon, 13 May 2019 10:43:09 -0700 Subject: [PATCH 19/24] Adds AsyncMock docs and tests for iterators and context managers. --- Doc/library/unittest.mock.rst | 159 ++++++++++++++++ Lib/unittest/mock.py | 79 ++++---- Lib/unittest/test/testmock/testasync.py | 172 +++++++++++++++++- .../2018-09-13-20-33-24.bpo-26467.cahAk3.rst | 1 - 4 files changed, 364 insertions(+), 47 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index ed00ee6d0c2d83..a8d05b216c0628 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -851,6 +851,165 @@ object:: >>> p.assert_called_once_with() +.. class:: AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs) + + An asynchronous version of :class:`Mock`. The :class:`AsyncMock` object will + behave so the object is recognized as an async function, and the result of a + call is an awaitable. + + >>> mock = AsyncMock() + >>> asyncio.iscoroutinefunction(mock) + True + >>> inspect.isawaitable(mock()) + True + + The result of ``mock()`` is an async function which will have the outcome + of ``side_effect`` or ``return_value``: + + - if ``side_effect`` is a function, the async function will return the + result of that function, + - if ``side_effect`` is an exception, the async function will raise the + exception, + - if ``side_effect`` is an iterable, the async function will return the + next value of the iterable, however, if the sequence of result is + exhausted, ``StopIteration`` is raised immediately, + - if ``side_effect`` is not defined, the async function will return the + value defined by ``return_value``, hence, by default, the async function + returns a new :class:`AsyncMock` object. + + + Setting the *spec* of a :class:`Mock` or :class:`MagicMock` to an async function + will result in a coroutine object being returned after calling. + + >>> async def async_func(): pass + ... + >>> mock = MagicMock(async_func) + >>> mock + + >>> mock() + + + .. method:: assert_awaited() + + Assert that the mock was awaited at least once. + + >>> mock = AsyncMock() + >>> async def main(): + ... await mock() + ... + >>> asyncio.run(main()) + >>> mock.assert_awaited() + >>> mock_2 = AsyncMock() + >>> mock_2.assert_awaited() + Traceback (most recent call last): + ... + AssertionError: Expected mock to have been awaited. + + .. method:: assert_awaited_once() + + Assert that the mock was awaited exactly once. + + >>> mock = AsyncMock() + >>> async def main(): + ... await mock() + ... + >>> asyncio.run(main()) + >>> mock.assert_awaited_once() + >>> asyncio.run(main()) + >>> mock.method.assert_awaited_once() + Traceback (most recent call last): + ... + AssertionError: Expected mock to have been awaited once. Awaited 2 times. + + .. method:: assert_awaited_with(*args, **kwargs) + + Assert that the last await was with the specified arguments. + + >>> mock = AsyncMock() + >>> async def main(*args, **kwargs): + ... await mock(*args, **kwargs) + ... + >>> asyncio.run(main('foo', bar='bar')) + >>> mock.assert_awaited_with('foo', bar='bar') + >>> mock.assert_awaited_with('other') + Traceback (most recent call last): + ... + AssertionError: expected call not found. + Expected: mock('other') + Actual: mock('foo', bar='bar') + + .. method:: assert_awaited_once_with(*args, **kwargs) + + Assert that the mock was awaited exactly once and with the specified + arguments. + + >>> mock = AsyncMock() + >>> async def main(*args, **kwargs): + ... await mock(*args, **kwargs) + ... + >>> asyncio.run(main('foo', bar='bar')) + >>> mock.assert_awaited_once_with('foo', bar='bar') + >>> asyncio.run(main('foo', bar='bar')) + >>> mock.assert_awaited_once_with('foo', bar='bar') + Traceback (most recent call last): + ... + AssertionError: Expected mock to have been awaited once. Awaited 2 times. + + .. method:: assert_any_await(*args, **kwargs) + + Assert the mock has ever been awaited with the specified arguments. + + >>> mock = AsyncMock() + >>> async def main(*args, **kwargs): + ... await mock(*args, **kwargs) + ... + >>> asyncio.run(main('foo', bar='bar')) + >>> asyncio.run(main('hello')) + >>> mock.assert_any_await('foo', bar='bar') + >>> mock.assert_any_await('other') + Traceback (most recent call last): + ... + AssertionError: mock('other') await not found + + .. method:: assert_has_awaits(calls, any_order=False) + + Assert the mock has been awaited with the specified calls. + The :attr:`await_args_list` list is checked for the awaits. + + If `any_order` is False (the default) then the awaits must be + sequential. There can be extra calls before or after the + specified awaits. + + If `any_order` is True then the awaits can be in any order, but + they must all appear in :attr:`await_args_list`. + + >>> mock = AsyncMock() + >>> async def main(*args, **kwargs): + ... await mock(*args, **kwargs) + ... + >>> calls = [call("foo"), call("bar")] + >>> mock.assert_has_calls(calls) + Traceback (most recent call last): + ... + AssertionError: Calls not found. + Expected: [call('foo'), call('bar')] + >>> asyncio.run(main('foo')) + >>> asyncio.run(main('bar')) + >>> mock.assert_has_calls(calls) + + .. method:: assert_not_awaited() + + Assert that the mock was never awaited. + + >>> mock = AsyncMock() + >>> mock.assert_not_awaited() + + .. method:: reset_mock(*args, **kwargs) + + See :func:`Mock.reset_mock`. Also sets :attr:`await_count` to 0, + :attr:`await_args` to None, and clears the :attr:`await_args_list`. + + Calling ~~~~~~~ diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index f1687ae8d559df..181a195afd8c38 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -1818,6 +1818,7 @@ def method(self, *args, **kw): '__float__': 1.0, '__bool__': True, '__index__': 1, + '__aexit__': False, } @@ -1854,8 +1855,8 @@ def _get_async_iter(self): def __aiter__(): ret_val = self.__aiter__._mock_return_value if ret_val is DEFAULT: - return AsyncIterator([]) - return AsyncIterator(ret_val) + return _AsyncIterator(iter([])) + return _AsyncIterator(iter(ret_val)) return __aiter__ _side_effect_methods = { @@ -1927,8 +1928,33 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_set_magics() +class AsyncMagicMixin: + def __init__(self, *args, **kw): + self._mock_set_async_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__) -class MagicMock(MagicMixin, Mock): + _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 of most of the magic methods. You can use MagicMock without having to @@ -1968,32 +1994,6 @@ def __get__(self, obj, _type=None): return self.create_mock() -class AsyncMagicMixin: - def __init__(self, *args, **kw): - self._mock_set_async_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)) - - class AsyncMockMixin(Base): awaited = _delegating_property('awaited') await_count = _delegating_property('await_count') @@ -2052,13 +2052,13 @@ def assert_awaited(_mock_self): msg = f"Expected {self._mock_name or 'mock'} to have been awaited." raise AssertionError(msg) - def assert_awaited_once(_mock_self, *args, **kwargs): + def assert_awaited_once(_mock_self): """ Assert that the mock was awaited exactly once. """ self = _mock_self if not self.await_count == 1: - msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." f" Awaited {self.await_count} times.") raise AssertionError(msg) @@ -2088,7 +2088,7 @@ def assert_awaited_once_with(_mock_self, *args, **kwargs): """ self = _mock_self if not self.await_count == 1: - msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." f" Awaited {self.await_count} times.") raise AssertionError(msg) return self.assert_awaited_with(*args, **kwargs) @@ -2150,7 +2150,7 @@ def assert_not_awaited(_mock_self): """ self = _mock_self if self.await_count != 0: - msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once.", + msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once." f" Awaited {self.await_count} times.") raise AssertionError(msg) @@ -2167,10 +2167,10 @@ def reset_mock(self, *args, **kwargs): class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): """ Enhance :class:`Mock` with features allowing to mock - a async function. + an async function. The :class:`AsyncMock` object will behave so the object is - recognized as async function, and the result of a call as a async: + recognized as an async function, and the result of a call is an awaitable: >>> mock = AsyncMock() >>> asyncio.iscoroutinefunction(mock) @@ -2193,8 +2193,8 @@ class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): value defined by ``return_value``, hence, by default, the async function returns a new :class:`AsyncMock` object. - If the outcome of ``side_effect`` or ``return_value`` is an async function, the - mock async function obtained when the mock object is called will be this + If the outcome of ``side_effect`` or ``return_value`` is an async function, + the mock async function obtained when the mock object is called will be this async function itself (and not an async function returning an async function). @@ -2757,20 +2757,19 @@ async def _raise(exception): raise exception -class AsyncIterator: +class _AsyncIterator: """ Wraps an iterator in an asynchronous iterator. """ def __init__(self, iterator): self.iterator = iterator code_mock = NonCallableMock(spec_set=CodeType) - code_mock.co_flags = inspect.CO_ITERATOR_COROUTINE + code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE self.__dict__['__code__'] = code_mock def __aiter__(self): return self - # cannot use async before 3.7 async def __anext__(self): try: return next(self.iterator) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index ceb0b2510b07be..2674ffa7b73aed 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -129,6 +129,10 @@ def foo(): pass self.assertTrue(asyncio.iscoroutinefunction(mock)) self.assertTrue(inspect.iscoroutinefunction(mock)) + def test_future_isfuture(self): + mock = AsyncMock(asyncio.Future()) + self.assertIsInstance(mock, asyncio.Future) + class AsyncAutospecTest(unittest.TestCase): def test_is_AsyncMock_patch(self): @@ -156,6 +160,7 @@ def setUp(self): def tearDown(self): # Restore the original event loop policy. asyncio.events._event_loop_policy = self.old_policy + def test_spec_as_async_positional_magicmock(self): mock = MagicMock(async_func) self.assertIsInstance(mock, MagicMock) @@ -277,15 +282,42 @@ async def addition(self, var): self.assertEqual(output, 10) def test_add_side_effect_exception(self): - async def addition(self, var): + async def addition(var): return var + 1 mock = AsyncMock(addition, side_effect=Exception('err')) with self.assertRaises(Exception): asyncio.run(mock(5)) + def test_add_side_effect_function(self): + async def addition(var): + return var + 1 + mock = AsyncMock(side_effect=addition) + result = asyncio.run(mock(5)) + self.assertEqual(result, 6) + + def test_add_side_effect_iterable(self): + vals = [1, 2, 3] + mock = AsyncMock(side_effect=vals) + for item in vals: + self.assertEqual(item, asyncio.run(mock())) + + with self.assertRaises(RuntimeError) as e: + asyncio.run(mock()) + self.assertEqual( + e.exception, + RuntimeError('coroutine raised StopIteration') + ) + + +class AsyncContextManagerTest(unittest.TestCase): + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy -class AsyncMagicMethods(unittest.TestCase): - class AsyncContextManager: + class WithAsyncContextManager: def __init__(self): self.entered = False self.exited = False @@ -297,10 +329,93 @@ async def __aenter__(self, *args, **kwargs): async def __aexit__(self, *args, **kwargs): self.exited = True - class AsyncIterator(object): + def test_magic_methods_are_async_mocks(self): + mock = MagicMock(self.WithAsyncContextManager()) + self.assertIsInstance(mock.__aenter__, AsyncMock) + self.assertIsInstance(mock.__aexit__, AsyncMock) + + def test_mock_supports_async_context_manager(self): + called = False + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + async def use_context_manager(): + nonlocal called + async with mock_instance as result: + called = True + return result + + result = asyncio.run(use_context_manager()) + self.assertFalse(instance.entered) + self.assertFalse(instance.exited) + self.assertTrue(called) + self.assertTrue(mock_instance.entered) + self.assertTrue(mock_instance.exited) + self.assertTrue(mock_instance.__aenter__.called) + self.assertTrue(mock_instance.__aexit__.called) + self.assertIsNot(mock_instance, result) + self.assertIsInstance(result, AsyncMock) + + def test_mock_customize_async_context_manager(self): + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + expected_result = object() + mock_instance.__aenter__.return_value = expected_result + + async def use_context_manager(): + async with mock_instance as result: + return result + + self.assertIs(asyncio.run(use_context_manager()), expected_result) + + def test_mock_customize_async_context_manager_with_coroutine(self): + enter_called = False + exit_called = False + + async def enter_coroutine(*args): + nonlocal enter_called + enter_called = True + + async def exit_coroutine(*args): + nonlocal exit_called + exit_called = True + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + + mock_instance.__aenter__ = enter_coroutine + mock_instance.__aexit__ = exit_coroutine + + async def use_context_manager(): + async with mock_instance: + pass + + asyncio.run(use_context_manager()) + self.assertTrue(enter_called) + self.assertTrue(exit_called) + + def test_context_manager_raise_exception_by_default(self): + async def raise_in(context_manager): + async with context_manager: + raise TypeError() + + instance = self.WithAsyncContextManager() + mock_instance = MagicMock(instance) + with self.assertRaises(TypeError): + asyncio.run(raise_in(mock_instance)) + + +class AsyncIteratorTest(unittest.TestCase): + def setUp(self): + self.old_policy = asyncio.events._event_loop_policy + + def tearDown(self): + # Restore the original event loop policy. + asyncio.events._event_loop_policy = self.old_policy + + class WithAsyncIterator(object): def __init__(self): - self.iter_called = False - self.next_called = False self.items = ["foo", "NormalFoo", "baz"] def __aiter__(self): @@ -314,6 +429,51 @@ async def __anext__(self): raise StopAsyncIteration + def test_mock_aiter_and_anext(self): + instance = self.WithAsyncIterator() + mock_instance = MagicMock(instance) + + self.assertEqual(asyncio.iscoroutine(instance.__aiter__), + asyncio.iscoroutine(mock_instance.__aiter__)) + self.assertEqual(asyncio.iscoroutine(instance.__anext__), + asyncio.iscoroutine(mock_instance.__anext__)) + + iterator = instance.__aiter__() + if asyncio.iscoroutine(iterator): + iterator = asyncio.run(iterator) + + mock_iterator = mock_instance.__aiter__() + if asyncio.iscoroutine(mock_iterator): + mock_iterator = asyncio.run(mock_iterator) + + self.assertEqual(asyncio.iscoroutine(iterator.__aiter__), + asyncio.iscoroutine(mock_iterator.__aiter__)) + self.assertEqual(asyncio.iscoroutine(iterator.__anext__), + asyncio.iscoroutine(mock_iterator.__anext__)) + + # def test_mock_async_for(self): + # async def iterate(iterator): + # accumulator = [] + # async for item in iterator: + # accumulator.append(item) + # + # return accumulator + # + # expected = ["FOO", "BAR", "BAZ"] + # with self.subTest("iterate through default value"): + # mock_instance = MagicMock(self.WithAsyncIterator()) + # self.assertEqual([], asyncio.run(iterate(mock_instance))) + # + # with self.subTest("iterate through set return_value"): + # mock_instance = MagicMock(self.WithAsyncIterator()) + # mock_instance.__aiter__.return_value = expected[:] + # self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + # + # with self.subTest("iterate through set return_value iterator"): + # mock_instance = MagicMock(self.WithAsyncIterator()) + # mock_instance.__aiter__.return_value = iter(expected[:]) + # self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + class AsyncMockAssert(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst index ede9f7253d58ea..4cf3f2ae7ef7e6 100644 --- a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst +++ b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst @@ -1,3 +1,2 @@ - Added AsyncMock to support using unittest to mock asyncio coroutines. Patch by Lisa Roach. From 68dff1b3e66ecfe02fa4dc70cb8f6cb2180df7f2 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Mon, 13 May 2019 10:44:38 -0700 Subject: [PATCH 20/24] Uncomments commented out test. --- Lib/unittest/test/testmock/testasync.py | 44 ++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index 2674ffa7b73aed..e1665996590bc0 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -451,28 +451,28 @@ def test_mock_aiter_and_anext(self): self.assertEqual(asyncio.iscoroutine(iterator.__anext__), asyncio.iscoroutine(mock_iterator.__anext__)) - # def test_mock_async_for(self): - # async def iterate(iterator): - # accumulator = [] - # async for item in iterator: - # accumulator.append(item) - # - # return accumulator - # - # expected = ["FOO", "BAR", "BAZ"] - # with self.subTest("iterate through default value"): - # mock_instance = MagicMock(self.WithAsyncIterator()) - # self.assertEqual([], asyncio.run(iterate(mock_instance))) - # - # with self.subTest("iterate through set return_value"): - # mock_instance = MagicMock(self.WithAsyncIterator()) - # mock_instance.__aiter__.return_value = expected[:] - # self.assertEqual(expected, asyncio.run(iterate(mock_instance))) - # - # with self.subTest("iterate through set return_value iterator"): - # mock_instance = MagicMock(self.WithAsyncIterator()) - # mock_instance.__aiter__.return_value = iter(expected[:]) - # self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + def test_mock_async_for(self): + async def iterate(iterator): + accumulator = [] + async for item in iterator: + accumulator.append(item) + + return accumulator + + expected = ["FOO", "BAR", "BAZ"] + with self.subTest("iterate through default value"): + mock_instance = MagicMock(self.WithAsyncIterator()) + self.assertEqual([], asyncio.run(iterate(mock_instance))) + + with self.subTest("iterate through set return_value"): + mock_instance = MagicMock(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = expected[:] + self.assertEqual(expected, asyncio.run(iterate(mock_instance))) + + with self.subTest("iterate through set return_value iterator"): + mock_instance = MagicMock(self.WithAsyncIterator()) + mock_instance.__aiter__.return_value = iter(expected[:]) + self.assertEqual(expected, asyncio.run(iterate(mock_instance))) class AsyncMockAssert(unittest.TestCase): From 64301e2b0207542fae47e710fd32dd414b2d9db6 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Thu, 16 May 2019 21:17:56 -0700 Subject: [PATCH 21/24] Fixes based on comments. --- Doc/library/unittest.mock.rst | 64 ++++++++++++- Doc/whatsnew/3.8.rst | 4 + Lib/unittest/mock.py | 9 +- Lib/unittest/test/testmock/testasync.py | 116 ++++++++---------------- 4 files changed, 103 insertions(+), 90 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index a8d05b216c0628..9510d2e11d77fd 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -201,9 +201,11 @@ The Mock Class .. testsetup:: + import asyncio + import inspect import unittest from unittest.mock import sentinel, DEFAULT, ANY - from unittest.mock import patch, call, Mock, MagicMock, PropertyMock + from unittest.mock import patch, call, Mock, MagicMock, PropertyMock, AsyncMock from unittest.mock import mock_open :class:`Mock` is a flexible mock object intended to replace the use of stubs and @@ -885,9 +887,9 @@ object:: ... >>> mock = MagicMock(async_func) >>> mock - + >>> mock() - + .. method:: assert_awaited() @@ -976,11 +978,11 @@ object:: Assert the mock has been awaited with the specified calls. The :attr:`await_args_list` list is checked for the awaits. - If `any_order` is False (the default) then the awaits must be + If *any_order* is False (the default) then the awaits must be sequential. There can be extra calls before or after the specified awaits. - If `any_order` is True then the awaits can be in any order, but + If *any_order* is True then the awaits can be in any order, but they must all appear in :attr:`await_args_list`. >>> mock = AsyncMock() @@ -1009,6 +1011,58 @@ object:: See :func:`Mock.reset_mock`. Also sets :attr:`await_count` to 0, :attr:`await_args` to None, and clears the :attr:`await_args_list`. + .. attribute:: await_count + + An integer keeping track of how many times the mock object has been awaited. + + >>> mock = AsyncMock() + >>> async def main(): + ... await mock() + ... + >>> asyncio.run(main()) + >>> mock.await_count + 1 + >>> asyncio.run(main()) + >>> mock.await_count + 2 + + .. attribute:: await_args + + This is either ``None`` (if the mock hasn’t been awaited), or the arguments that + the mock was last awaited with. Functions the same as :attr:`Mock.call_args`. + + >>> mock = AsyncMock() + >>> async def main(*args): + ... await mock() + ... + >>> mock.await_args + >>> asyncio.run(main('foo')) + >>> mock.await_args + call('foo') + >>> asyncio.run(main('bar')) + >>> mock.await_args + call('bar') + + + .. attribute:: await_args_list + + This is a list of all the awaits made to the mock object in sequence (so the + length of the list is the number of times it has been awaited). Before any + awaits have been made it is an empty list. + + >>> mock = AsyncMock() + >>> async def main(*args): + ... await mock() + ... + >>> mock.await_args_list + [] + >>> asyncio.run(main('foo')) + >>> mock.await_args + [call('foo')] + >>> asyncio.run(main('bar')) + >>> mock.await_args_list + [call('foo'), call('bar')] + Calling ~~~~~~~ diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index d6388f8faaba4f..f3f9cedb5ce217 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -508,6 +508,10 @@ unicodedata unittest -------- +* XXX Added :class:`AsyncMock` to support an asynchronous version of :class:`Mock`. + Appropriate new assert functions for testing have been added as well. + (Contributed by Lisa Roach in :issue:`26467`). + * Added :func:`~unittest.addModuleCleanup()` and :meth:`~unittest.TestCase.addClassCleanup()` to unittest to support cleanups for :func:`~unittest.setUpModule()` and diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 181a195afd8c38..2f68b9ffa0d82e 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2429,7 +2429,6 @@ def call_list(self): call = _Call(from_kall=False) - def create_autospec(spec, spec_set=False, instance=False, _parent=None, _name=None, **kwargs): """Create a mock object using another object as a spec. Attributes on the @@ -2476,9 +2475,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # because we don't know what type they return _kwargs = {} elif is_async_func: - if instance: - raise RuntimeError("Instance can not be True when create_autospec " - "is mocking an async function") + # if instance: + # raise RuntimeError("Instance can not be True when create_autospec " + # "is mocking an async function") Klass = AsyncMock elif not _callable(spec): Klass = NonCallableMagicMock @@ -2501,7 +2500,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # recurse for functions mock = _set_signature(mock, spec) if is_async_func: - mock._is_coroutine = _is_coroutine + mock._is_coroutine = asyncio.coroutines._is_coroutine mock.await_count = 0 mock.await_args = None mock.await_args_list = _CallList() diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py index e1665996590bc0..a9aa1434b963f1 100644 --- a/Lib/unittest/test/testmock/testasync.py +++ b/Lib/unittest/test/testmock/testasync.py @@ -2,7 +2,12 @@ import inspect import unittest -from unittest.mock import call, AsyncMock, patch, MagicMock +from unittest.mock import call, AsyncMock, patch, MagicMock, create_autospec + + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + class AsyncClass: def __init__(self): @@ -28,14 +33,6 @@ def a(self): class AsyncPatchDecoratorTest(unittest.TestCase): - def setUp(self): - # Prevents altering the execution environment - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - def test_is_coroutine_function_patch(self): @patch.object(AsyncClass, 'async_method') def test_async(mock_method): @@ -61,19 +58,12 @@ def test_no_parent_attribute(mock_method): def test_is_AsyncMock_patch(self): @patch.object(AsyncClass, 'async_method') def test_async(mock_method): - self.assertTrue(isinstance(mock_method, AsyncMock)) + self.assertIsInstance(mock_method, AsyncMock) test_async() class AsyncPatchCMTest(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - def test_is_async_function_cm(self): def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: @@ -93,19 +83,12 @@ def test_async(): def test_is_AsyncMock_cm(self): def test_async(): with patch.object(AsyncClass, 'async_method') as mock_method: - self.assertTrue(isinstance(mock_method, AsyncMock)) + self.assertIsInstance(mock_method, AsyncMock) test_async() class AsyncMockTest(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - def test_iscoroutinefunction_default(self): mock = AsyncMock() self.assertTrue(asyncio.iscoroutinefunction(mock)) @@ -130,7 +113,12 @@ def foo(): pass self.assertTrue(inspect.iscoroutinefunction(mock)) def test_future_isfuture(self): - mock = AsyncMock(asyncio.Future()) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + fut = asyncio.Future() + loop.stop() + loop.close() + mock = AsyncMock(fut) self.assertIsInstance(mock, asyncio.Future) @@ -138,29 +126,26 @@ class AsyncAutospecTest(unittest.TestCase): def test_is_AsyncMock_patch(self): @patch(async_foo_name, autospec=True) def test_async(mock_method): - self.assertTrue(isinstance( - mock_method.async_method, - AsyncMock)) - self.assertTrue(isinstance(mock_method, MagicMock)) + self.assertIsInstance(mock_method.async_method, AsyncMock) + self.assertIsInstance(mock_method, MagicMock) @patch(async_foo_name, autospec=True) def test_normal_method(mock_method): - self.assertTrue(isinstance( - mock_method.normal_method, - MagicMock)) + self.assertIsInstance(mock_method.normal_method, MagicMock) test_async() test_normal_method() + def test_create_autospec_instance(self): + with self.assertRaises(RuntimeError): + create_autospec(async_func, instance=True) -class AsyncSpecTest(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy + def test_create_autospec(self): + spec = create_autospec(async_func) + self.assertTrue(asyncio.iscoroutinefunction(spec)) - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy +class AsyncSpecTest(unittest.TestCase): def test_spec_as_async_positional_magicmock(self): mock = MagicMock(async_func) self.assertIsInstance(mock, MagicMock) @@ -206,23 +191,22 @@ def test_spec_as_normal_positional_AsyncMock(self): def test_spec_async_mock(self): @patch.object(AsyncClass, 'async_method', spec=True) def test_async(mock_method): - self.assertTrue(isinstance(mock_method, AsyncMock)) + self.assertIsInstance(mock_method, AsyncMock) test_async() def test_spec_parent_not_async_attribute_is(self): @patch(async_foo_name, spec=True) def test_async(mock_method): - self.assertTrue(isinstance(mock_method, MagicMock)) - self.assertTrue(isinstance(mock_method.async_method, - AsyncMock)) + self.assertIsInstance(mock_method, MagicMock) + self.assertIsInstance(mock_method.async_method, AsyncMock) test_async() def test_target_async_spec_not(self): @patch.object(AsyncClass, 'async_method', spec=NormalClass.a) def test_async_attribute(mock_method): - self.assertTrue(isinstance(mock_method, MagicMock)) + self.assertIsInstance(mock_method, MagicMock) self.assertFalse(inspect.iscoroutine(mock_method)) self.assertFalse(inspect.isawaitable(mock_method)) @@ -231,15 +215,14 @@ def test_async_attribute(mock_method): def test_target_not_async_spec_is(self): @patch.object(NormalClass, 'a', spec=async_func) def test_attribute_not_async_spec_is(mock_async_func): - self.assertTrue(isinstance(mock_async_func, AsyncMock)) + self.assertIsInstance(mock_async_func, AsyncMock) test_attribute_not_async_spec_is() def test_spec_async_attributes(self): @patch(normal_foo_name, spec=AsyncClass) def test_async_attributes_coroutines(MockNormalClass): - self.assertTrue(isinstance(MockNormalClass.async_method, - AsyncMock)) - self.assertTrue(isinstance(MockNormalClass, MagicMock)) + self.assertIsInstance(MockNormalClass.async_method, AsyncMock) + self.assertIsInstance(MockNormalClass, MagicMock) test_async_attributes_coroutines() @@ -248,30 +231,23 @@ class AsyncSpecSetTest(unittest.TestCase): def test_is_AsyncMock_patch(self): @patch.object(AsyncClass, 'async_method', spec_set=True) def test_async(async_method): - self.assertTrue(isinstance(async_method, AsyncMock)) + self.assertIsInstance(async_method, AsyncMock) def test_is_async_AsyncMock(self): mock = AsyncMock(spec_set=AsyncClass.async_method) self.assertTrue(asyncio.iscoroutinefunction(mock)) - self.assertTrue(isinstance(mock, AsyncMock)) + self.assertIsInstance(mock, AsyncMock) def test_is_child_AsyncMock(self): mock = MagicMock(spec_set=AsyncClass) self.assertTrue(asyncio.iscoroutinefunction(mock.async_method)) self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method)) - self.assertTrue(isinstance(mock.async_method, AsyncMock)) - self.assertTrue(isinstance(mock.normal_method, MagicMock)) - self.assertTrue(isinstance(mock, MagicMock)) + self.assertIsInstance(mock.async_method, AsyncMock) + self.assertIsInstance(mock.normal_method, MagicMock) + self.assertIsInstance(mock, MagicMock) class AsyncArguments(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - def test_add_return_value(self): async def addition(self, var): return var + 1 @@ -310,13 +286,6 @@ def test_add_side_effect_iterable(self): class AsyncContextManagerTest(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - class WithAsyncContextManager: def __init__(self): self.entered = False @@ -407,13 +376,6 @@ async def raise_in(context_manager): class AsyncIteratorTest(unittest.TestCase): - def setUp(self): - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy - class WithAsyncIterator(object): def __init__(self): self.items = ["foo", "NormalFoo", "baz"] @@ -476,14 +438,8 @@ async def iterate(iterator): class AsyncMockAssert(unittest.TestCase): - def setUp(self): self.mock = AsyncMock() - self.old_policy = asyncio.events._event_loop_policy - - def tearDown(self): - # Restore the original event loop policy. - asyncio.events._event_loop_policy = self.old_policy async def _runnable_test(self, *args): if not args: From c7cd95e1881556453e9cd9a99946261ab38ac8ca Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Fri, 17 May 2019 15:41:52 -0700 Subject: [PATCH 22/24] Fixes broken docs. --- Doc/library/unittest.mock.rst | 4 ++-- Lib/unittest/mock.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 9510d2e11d77fd..3a3276f5912cfa 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -1033,7 +1033,7 @@ object:: >>> mock = AsyncMock() >>> async def main(*args): - ... await mock() + ... await mock(*args) ... >>> mock.await_args >>> asyncio.run(main('foo')) @@ -1052,7 +1052,7 @@ object:: >>> mock = AsyncMock() >>> async def main(*args): - ... await mock() + ... await mock(*args) ... >>> mock.await_args_list [] diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 2f68b9ffa0d82e..43d95c3f6f62db 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2475,9 +2475,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # because we don't know what type they return _kwargs = {} elif is_async_func: - # if instance: - # raise RuntimeError("Instance can not be True when create_autospec " - # "is mocking an async function") + if instance: + raise RuntimeError("Instance can not be True when create_autospec " + "is mocking an async function") Klass = AsyncMock elif not _callable(spec): Klass = NonCallableMagicMock From 033f7d3c85df4f9074bfcddb4f888abd8175aa23 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sat, 18 May 2019 15:45:03 -0700 Subject: [PATCH 23/24] Fixes broken doc await_arg. --- Doc/library/unittest.mock.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 3a3276f5912cfa..21e4709f81609e 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -1057,7 +1057,7 @@ object:: >>> mock.await_args_list [] >>> asyncio.run(main('foo')) - >>> mock.await_args + >>> mock.await_args_list [call('foo')] >>> asyncio.run(main('bar')) >>> mock.await_args_list From 2fef02c20f294b23d836ed217217ed90312a2d58 Mon Sep 17 00:00:00 2001 From: Lisa Roach Date: Sat, 18 May 2019 15:59:08 -0700 Subject: [PATCH 24/24] Adds shoutout to Martin Richard for asynctest. --- Lib/unittest/mock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 43d95c3f6f62db..2ebbcbe369631b 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2202,6 +2202,8 @@ class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock): case, the :class:`Mock` object behavior is the same as with an :class:`.Mock` object: the wrapped object may have methods defined as async function functions. + + Based on Martin Richard's asyntest project. """ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy