from functools import wraps, partial
+class InvalidSpecError(Exception):
+ """Indicates that an invalid value was used as a mock spec."""
+
+
_builtins = {name for name in dir(builtins) if not name.startswith('_')}
FILTER_DIR = True
self._mock_children[name] = result
elif isinstance(result, _SpecState):
- result = create_autospec(
- result.spec, result.spec_set, result.instance,
- result.parent, result.name
- )
+ try:
+ result = create_autospec(
+ result.spec, result.spec_set, result.instance,
+ result.parent, result.name
+ )
+ except InvalidSpecError:
+ target_name = self.__dict__['_mock_name'] or self
+ raise InvalidSpecError(
+ f'Cannot autospec attr {name!r} from target '
+ f'{target_name!r} as it has already been mocked out. '
+ f'[target={self!r}, attr={result.spec!r}]')
self._mock_children[name] = result
return result
)
if not unsafe:
_check_spec_arg_typos(kwargs)
+ if _is_instance_mock(spec):
+ raise InvalidSpecError(
+ f'Cannot spec attr {attribute!r} as the spec '
+ f'has already been mocked out. [spec={spec!r}]')
+ if _is_instance_mock(spec_set):
+ raise InvalidSpecError(
+ f'Cannot spec attr {attribute!r} as the spec_set '
+ f'target has already been mocked out. [spec_set={spec_set!r}]')
self.getter = getter
self.attribute = attribute
if autospec is True:
autospec = original
+ if _is_instance_mock(self.target):
+ raise InvalidSpecError(
+ f'Cannot autospec attr {self.attribute!r} as the patch '
+ f'target has already been mocked out. '
+ f'[target={self.target!r}, attr={autospec!r}]')
+ if _is_instance_mock(autospec):
+ target_name = getattr(self.target, '__name__', self.target)
+ raise InvalidSpecError(
+ f'Cannot autospec attr {self.attribute!r} from target '
+ f'{target_name!r} as it has already been mocked out. '
+ f'[target={self.target!r}, attr={autospec!r}]')
+
new = create_autospec(autospec, spec_set=spec_set,
_name=self.attribute, **kwargs)
elif kwargs:
spec = type(spec)
is_type = isinstance(spec, type)
+ if _is_instance_mock(spec):
+ raise InvalidSpecError(f'Cannot autospec a Mock object. '
+ f'[object={spec!r}]')
is_async_func = _is_async_func(spec)
_kwargs = {'spec': spec}
if spec_set:
call, DEFAULT, patch, sentinel,
MagicMock, Mock, NonCallableMock,
NonCallableMagicMock, AsyncMock, _Call, _CallList,
- create_autospec
+ create_autospec, InvalidSpecError
)
self.assertRaisesRegex(ValueError, 'Bazinga!', mock)
+ def test_autospec_mock(self):
+ class A(object):
+ class B(object):
+ C = None
+
+ with mock.patch.object(A, 'B'):
+ with self.assertRaisesRegex(InvalidSpecError,
+ "Cannot autospec attr 'B' from target <MagicMock spec='A'"):
+ create_autospec(A).B
+ with self.assertRaisesRegex(InvalidSpecError,
+ "Cannot autospec attr 'B' from target 'A'"):
+ mock.patch.object(A, 'B', autospec=True).start()
+ with self.assertRaisesRegex(InvalidSpecError,
+ "Cannot autospec attr 'C' as the patch target "):
+ mock.patch.object(A.B, 'C', autospec=True).start()
+ with self.assertRaisesRegex(InvalidSpecError,
+ "Cannot spec attr 'B' as the spec "):
+ mock.patch.object(A, 'B', spec=A.B).start()
+ with self.assertRaisesRegex(InvalidSpecError,
+ "Cannot spec attr 'B' as the spec_set "):
+ mock.patch.object(A, 'B', spec_set=A.B).start()
+
def test_reset_mock(self):
parent = Mock()
spec = ["something"]
self.obj_with_bool_func = unittest.mock.MagicMock()
obj = Something()
- with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass
+ with unittest.mock.patch.object(obj, 'obj_with_bool_func', spec=object): pass
self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0)