mirror of https://github.com/python/cpython
bpo-35330: Don't call the wrapped object if `side_effect` is set (GH10973)
* tests: Further validate `wraps` functionality in `unittest.mock.Mock` Add more tests to validate how `wraps` interacts with other features of mocks. * Don't call the wrapped object if `side_effect` is set When a object is wrapped using `Mock(wraps=...)`, if an user sets a `side_effect` in one of their methods, return the value of `side_effect` and don't call the original object. * Refactor what to be called on `mock_call` When a `Mock` is called, it should return looking up in the following order: `side_effect`, `return_value`, `wraps`. If any of the first two return `mock.DEFAULT`, lookup in the next option. It makes no sense to check for `wraps` returning default, as it is supposed to be the original implementation and there is nothing to fallback to.
This commit is contained in:
parent
3cf74384b5
commit
f05df0a4b6
|
@ -1027,28 +1027,27 @@ class CallableMixin(Base):
|
|||
break
|
||||
seen.add(_new_parent_id)
|
||||
|
||||
ret_val = DEFAULT
|
||||
effect = self.side_effect
|
||||
if effect is not None:
|
||||
if _is_exception(effect):
|
||||
raise effect
|
||||
|
||||
if not _callable(effect):
|
||||
elif not _callable(effect):
|
||||
result = next(effect)
|
||||
if _is_exception(result):
|
||||
raise result
|
||||
if result is DEFAULT:
|
||||
result = self.return_value
|
||||
else:
|
||||
result = effect(*args, **kwargs)
|
||||
|
||||
if result is not DEFAULT:
|
||||
return result
|
||||
|
||||
ret_val = effect(*args, **kwargs)
|
||||
if self._mock_return_value is not DEFAULT:
|
||||
return self.return_value
|
||||
|
||||
if (self._mock_wraps is not None and
|
||||
self._mock_return_value is DEFAULT):
|
||||
if self._mock_wraps is not None:
|
||||
return self._mock_wraps(*args, **kwargs)
|
||||
if ret_val is DEFAULT:
|
||||
ret_val = self.return_value
|
||||
return ret_val
|
||||
|
||||
return self.return_value
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -558,6 +558,16 @@ class MockTest(unittest.TestCase):
|
|||
real.assert_called_with(1, 2, fish=3)
|
||||
|
||||
|
||||
def test_wraps_prevents_automatic_creation_of_mocks(self):
|
||||
class Real(object):
|
||||
pass
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
|
||||
self.assertRaises(AttributeError, lambda: mock.new_attr())
|
||||
|
||||
|
||||
def test_wraps_call_with_nondefault_return_value(self):
|
||||
real = Mock()
|
||||
|
||||
|
@ -584,6 +594,118 @@ class MockTest(unittest.TestCase):
|
|||
self.assertEqual(result, Real.attribute.frog())
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_side_effect_iterable_with_default(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
return sentinel.ORIGINAL_VALUE
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = [sentinel.VALUE1, DEFAULT]
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE1)
|
||||
self.assertEqual(mock.method(), sentinel.ORIGINAL_VALUE)
|
||||
self.assertRaises(StopIteration, mock.method)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_side_effect_iterable(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2]
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE1)
|
||||
self.assertEqual(mock.method(), sentinel.VALUE2)
|
||||
self.assertRaises(StopIteration, mock.method)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_side_effect_exception(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = RuntimeError
|
||||
|
||||
self.assertRaises(RuntimeError, mock.method)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_side_effect_function(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def side_effect():
|
||||
return sentinel.VALUE
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = side_effect
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_return_value(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.return_value = sentinel.VALUE
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_return_value_and_side_effect(self):
|
||||
# side_effect should always take precedence over return_value.
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2]
|
||||
mock.method.return_value = sentinel.WRONG_VALUE
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE1)
|
||||
self.assertEqual(mock.method(), sentinel.VALUE2)
|
||||
self.assertRaises(StopIteration, mock.method)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_return_value_and_side_effect2(self):
|
||||
# side_effect can return DEFAULT to default to return_value
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = lambda: DEFAULT
|
||||
mock.method.return_value = sentinel.VALUE
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE)
|
||||
|
||||
|
||||
def test_customize_wrapped_object_with_return_value_and_side_effect_default(self):
|
||||
class Real(object):
|
||||
def method(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
real = Real()
|
||||
mock = Mock(wraps=real)
|
||||
mock.method.side_effect = [sentinel.VALUE1, DEFAULT]
|
||||
mock.method.return_value = sentinel.RETURN
|
||||
|
||||
self.assertEqual(mock.method(), sentinel.VALUE1)
|
||||
self.assertEqual(mock.method(), sentinel.RETURN)
|
||||
self.assertRaises(StopIteration, mock.method)
|
||||
|
||||
|
||||
def test_exceptional_side_effect(self):
|
||||
mock = Mock(side_effect=AttributeError)
|
||||
self.assertRaises(AttributeError, mock)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
When a :class:`Mock` instance was used to wrap an object, if `side_effect`
|
||||
is used in one of the mocks of it methods, don't call the original
|
||||
implementation and return the result of using the side effect the same way
|
||||
that it is done with return_value.
|
Loading…
Reference in New Issue