From 2cd48738ba0a593a6edf6f4f41b420ead3719e71 Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Sat, 21 Apr 2012 15:52:11 +0100 Subject: [PATCH 1/3] Closes issue 14636. mock objects raise exceptions from an iterable side_effect --- Doc/library/unittest.mock-examples.rst | 50 -------------------------- Doc/library/unittest.mock.rst | 14 ++++++++ Lib/unittest/mock.py | 10 ++++-- Lib/unittest/test/testmock/testmock.py | 10 ++++++ 4 files changed, 31 insertions(+), 53 deletions(-) diff --git a/Doc/library/unittest.mock-examples.rst b/Doc/library/unittest.mock-examples.rst index 0fc32d2077b..8e1e88a147c 100644 --- a/Doc/library/unittest.mock-examples.rst +++ b/Doc/library/unittest.mock-examples.rst @@ -838,56 +838,6 @@ and the `return_value` will use your subclass automatically. That means all children of a `CopyingMock` will also have the type `CopyingMock`. -Multiple calls with different effects -------------------------------------- - -Handling code that needs to behave differently on subsequent calls during the -test can be tricky. For example you may have a function that needs to raise -an exception the first time it is called but returns a response on the second -call (testing retry behaviour). - -One approach is to use a :attr:`side_effect` function that replaces itself. The -first time it is called the `side_effect` sets a new `side_effect` that will -be used for the second call. It then raises an exception: - - >>> def side_effect(*args): - ... def second_call(*args): - ... return 'response' - ... mock.side_effect = second_call - ... raise Exception('boom') - ... - >>> mock = Mock(side_effect=side_effect) - >>> mock('first') - Traceback (most recent call last): - ... - Exception: boom - >>> mock('second') - 'response' - >>> mock.assert_called_with('second') - -Another perfectly valid way would be to pop return values from a list. If the -return value is an exception, raise it instead of returning it: - - >>> returns = [Exception('boom'), 'response'] - >>> def side_effect(*args): - ... result = returns.pop(0) - ... if isinstance(result, Exception): - ... raise result - ... return result - ... - >>> mock = Mock(side_effect=side_effect) - >>> mock('first') - Traceback (most recent call last): - ... - Exception: boom - >>> mock('second') - 'response' - >>> mock.assert_called_with('second') - -Which approach you prefer is a matter of taste. The first approach is actually -a line shorter but maybe the second approach is more readable. - - Nesting Patches --------------- diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index ed6775a4878..12b0275aab6 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -823,6 +823,20 @@ a `StopIteration` is raised): ... StopIteration +If any members of the iterable are exceptions they will be raised instead of +returned:: + + >>> iterable = (33, ValueError, 66) + >>> m = MagicMock(side_effect=iterable) + >>> m() + 33 + >>> m() + Traceback (most recent call last): + ... + ValueError + >>> m() + 66 + .. _deleting-attributes: diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 8cedcb4e718..8378b0ff957 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -891,7 +891,10 @@ class CallableMixin(Base): raise effect if not _callable(effect): - return next(effect) + result = next(effect) + if _is_exception(result): + raise result + return result ret_val = effect(*args, **kwargs) if ret_val is DEFAULT: @@ -931,8 +934,9 @@ class Mock(CallableMixin, NonCallableMock): arguments as the mock, and unless it returns `DEFAULT`, the return value of this function is used as the return value. - Alternatively `side_effect` can be an exception class or instance. In - this case the exception will be raised when the mock is called. + If `side_effect` is an iterable then each call to the mock will return + the next value from the iterable. If any of the members of the iterable + are exceptions they will be raised instead of returned. If `side_effect` is an iterable then each call to the mock will return the next value from the iterable. diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index ae9822eac66..64fd1a11c9b 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -868,6 +868,16 @@ class MockTest(unittest.TestCase): self.assertRaises(StopIteration, mock) + def test_side_effect_iterator_exceptions(self): + for Klass in Mock, MagicMock: + iterable = (ValueError, 3, KeyError, 6) + m = Klass(side_effect=iterable) + self.assertRaises(ValueError, m) + self.assertEqual(m(), 3) + self.assertRaises(KeyError, m) + self.assertEqual(m(), 6) + + def test_side_effect_setting_iterator(self): mock = Mock() mock.side_effect = iter([1, 2, 3]) From 3af125a4aaef5b34d8abf9d50958077473706954 Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Sat, 21 Apr 2012 18:22:28 +0100 Subject: [PATCH 2/3] Closes issue 14634. unittest.mock.create_autospec now supports keyword only arguments. --- Doc/library/inspect.rst | 13 +++++++++---- Lib/unittest/mock.py | 11 ++++++++--- Lib/unittest/test/testmock/testhelpers.py | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index ac6ae994369..90b7220a6b0 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -440,11 +440,16 @@ Classes and functions locals dictionary of the given frame. -.. function:: formatargspec(args[, varargs, varkw, defaults, formatarg, formatvarargs, formatvarkw, formatvalue]) +.. function:: formatargspec(args[, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations, formatarg, formatvarargs, formatvarkw, formatvalue, formatreturns, formatannotations]) - Format a pretty argument spec from the four values returned by - :func:`getargspec`. The format\* arguments are the corresponding optional - formatting functions that are called to turn names and values into strings. + Format a pretty argument spec from the values returned by + :func:`getargspec` or :func:`getfullargspec`. + + The first seven arguments are (``args``, ``varargs``, ``varkw``, + ``defaults``, ``kwonlyargs``, ``kwonlydefaults``, ``annotations``). The + other five arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The last argument + is an optional function to format the sequence of arguments. .. function:: formatargvalues(args[, varargs, varkw, locals, formatarg, formatvarargs, formatvarkw, formatvalue]) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 8378b0ff957..8f592ab7646 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -78,11 +78,15 @@ def _getsignature(func, skipfirst, instance=False): return try: - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) + argspec = inspect.getfullargspec(func) except TypeError: # C function / method, possibly inherited object().__init__ return + # not using annotations + regargs, varargs, varkw, defaults, kwonly, kwonlydef, ann = argspec + + # instance methods and classmethods need to lose the self argument if getattr(func, '__self__', None) is not None: regargs = regargs[1:] @@ -90,8 +94,9 @@ def _getsignature(func, skipfirst, instance=False): # this condition and the above one are never both True - why? regargs = regargs[1:] - signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: "") + signature = inspect.formatargspec( + regargs, varargs, varkw, defaults, + kwonly, kwonlydef, ann, formatvalue=lambda value: "") return signature[1:-1], func diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 4c43f87dbcd..7a7145ecf61 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -367,7 +367,7 @@ class SpecSignatureTest(unittest.TestCase): def test_create_autospec_unbound_methods(self): - # see issue 128 + # see mock issue 128 # this is expected to fail until the issue is fixed return class Foo(object): @@ -391,6 +391,19 @@ class SpecSignatureTest(unittest.TestCase): self.assertEqual(m.a, '3') + def test_create_autospec_keyword_only_arguments(self): + def foo(a, *, b=None): + pass + + m = create_autospec(foo) + m(1) + m.assert_called_with(1) + self.assertRaises(TypeError, m, 1, 2) + + m(2, b=3) + m.assert_called_with(2, b=3) + + def test_function_as_instance_attribute(self): obj = SomeClass() def f(a): From 9cf5c9d85e3b904a76acac3cbcdc0d45c4e486b5 Mon Sep 17 00:00:00 2001 From: Michael Foord Date: Sat, 21 Apr 2012 18:32:56 +0100 Subject: [PATCH 3/3] Remove incorrect comment --- Lib/unittest/mock.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 8f592ab7646..a94acd638e3 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -83,7 +83,6 @@ def _getsignature(func, skipfirst, instance=False): # C function / method, possibly inherited object().__init__ return - # not using annotations regargs, varargs, varkw, defaults, kwonly, kwonlydef, ann = argspec