mirror of https://github.com/python/cpython
Closes issue 14636. mock objects raise exceptions from an iterable side_effect
This commit is contained in:
parent
24117a748b
commit
2cd48738ba
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Reference in New Issue