summaryrefslogtreecommitdiffstats
path: root/Lib/unittest/mock.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/unittest/mock.py')
-rw-r--r--Lib/unittest/mock.py43
1 files changed, 41 insertions, 2 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 94e3442..9302ded 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -18,6 +18,7 @@ __all__ = (
'NonCallableMagicMock',
'mock_open',
'PropertyMock',
+ 'seal',
)
@@ -382,6 +383,7 @@ class NonCallableMock(Base):
__dict__['_mock_name'] = name
__dict__['_mock_new_name'] = _new_name
__dict__['_mock_new_parent'] = _new_parent
+ __dict__['_mock_sealed'] = False
if spec_set is not None:
spec = spec_set
@@ -608,7 +610,7 @@ class NonCallableMock(Base):
return result
- def __repr__(self):
+ def _extract_mock_name(self):
_name_list = [self._mock_new_name]
_parent = self._mock_new_parent
last = self
@@ -638,7 +640,10 @@ class NonCallableMock(Base):
if _name_list[1] not in ('()', '().'):
_first += '.'
_name_list[0] = _first
- name = ''.join(_name_list)
+ return ''.join(_name_list)
+
+ def __repr__(self):
+ name = self._extract_mock_name()
name_string = ''
if name not in ('mock', 'mock.'):
@@ -705,6 +710,11 @@ class NonCallableMock(Base):
else:
if _check_and_set_parent(self, value, name, name):
self._mock_children[name] = value
+
+ if self._mock_sealed and not hasattr(self, name):
+ mock_name = f'{self._extract_mock_name()}.{name}'
+ raise AttributeError(f'Cannot set {mock_name}')
+
return object.__setattr__(self, name, value)
@@ -888,6 +898,12 @@ class NonCallableMock(Base):
klass = Mock
else:
klass = _type.__mro__[1]
+
+ if self._mock_sealed:
+ attribute = "." + kw["name"] if "name" in kw else "()"
+ mock_name = self._extract_mock_name() + attribute
+ raise AttributeError(mock_name)
+
return klass(**kw)
@@ -2401,3 +2417,26 @@ class PropertyMock(Mock):
return self()
def __set__(self, obj, val):
self(val)
+
+
+def seal(mock):
+ """Disable the automatic generation of "submocks"
+
+ Given an input Mock, seals it to ensure no further mocks will be generated
+ when accessing an attribute that was not already defined.
+
+ Submocks are defined as all mocks which were created DIRECTLY from the
+ parent. If a mock is assigned to an attribute of an existing mock,
+ it is not considered a submock.
+
+ """
+ mock._mock_sealed = True
+ for attr in dir(mock):
+ try:
+ m = getattr(mock, attr)
+ except AttributeError:
+ continue
+ if not isinstance(m, NonCallableMock):
+ continue
+ if m._mock_new_parent is mock:
+ seal(m)