changeset: 76446:c310233b1d64 user: Michael Foord date: Sat Apr 21 15:52:11 2012 +0100 files: Doc/library/unittest.mock-examples.rst Doc/library/unittest.mock.rst Lib/unittest/mock.py Lib/unittest/test/testmock/testmock.py description: Closes issue 14636. mock objects raise exceptions from an iterable side_effect diff -r c820aa9c0c00 -r c310233b1d64 Doc/library/unittest.mock-examples.rst --- a/Doc/library/unittest.mock-examples.rst Fri Apr 20 18:04:03 2012 -0400 +++ b/Doc/library/unittest.mock-examples.rst Sat Apr 21 15:52:11 2012 +0100 @@ -838,56 +838,6 @@ 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 -r c820aa9c0c00 -r c310233b1d64 Doc/library/unittest.mock.rst --- a/Doc/library/unittest.mock.rst Fri Apr 20 18:04:03 2012 -0400 +++ b/Doc/library/unittest.mock.rst Sat Apr 21 15:52:11 2012 +0100 @@ -823,6 +823,20 @@ ... 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 -r c820aa9c0c00 -r c310233b1d64 Lib/unittest/mock.py --- a/Lib/unittest/mock.py Fri Apr 20 18:04:03 2012 -0400 +++ b/Lib/unittest/mock.py Sat Apr 21 15:52:11 2012 +0100 @@ -891,7 +891,10 @@ 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 @@ 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 -r c820aa9c0c00 -r c310233b1d64 Lib/unittest/test/testmock/testmock.py --- a/Lib/unittest/test/testmock/testmock.py Fri Apr 20 18:04:03 2012 -0400 +++ b/Lib/unittest/test/testmock/testmock.py Sat Apr 21 15:52:11 2012 +0100 @@ -868,6 +868,16 @@ 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])

AltStyle によって変換されたページ (->オリジナル) /