Skip to content

Commit c8319a3

Browse files
sobolevncdce8p
andauthored
gh-135368: Fix mocks on dataclass specs with instance=True (#135421)
* gh-135368: Fix mocks on dataclass specs with `instance=True` * Extend dataclass mock_methods --------- Co-authored-by: Marc Mueller <[email protected]>
1 parent c2bb3f9 commit c8319a3

File tree

3 files changed

+34
-2
lines changed

3 files changed

+34
-2
lines changed

Lib/test/test_unittest/testmock/testhelpers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ def __post_init__(self):
10501050
create_autospec(WithPostInit()),
10511051
]:
10521052
with self.subTest(mock=mock):
1053+
self.assertIsInstance(mock, WithPostInit)
10531054
self.assertIsInstance(mock.a, int)
10541055
self.assertIsInstance(mock.b, int)
10551056

@@ -1072,6 +1073,7 @@ class WithDefault:
10721073
create_autospec(WithDefault(1)),
10731074
]:
10741075
with self.subTest(mock=mock):
1076+
self.assertIsInstance(mock, WithDefault)
10751077
self.assertIsInstance(mock.a, int)
10761078
self.assertIsInstance(mock.b, int)
10771079

@@ -1087,6 +1089,7 @@ def b(self) -> int:
10871089
create_autospec(WithMethod(1)),
10881090
]:
10891091
with self.subTest(mock=mock):
1092+
self.assertIsInstance(mock, WithMethod)
10901093
self.assertIsInstance(mock.a, int)
10911094
mock.b.assert_not_called()
10921095

@@ -1102,11 +1105,29 @@ class WithNonFields:
11021105
create_autospec(WithNonFields(1)),
11031106
]:
11041107
with self.subTest(mock=mock):
1108+
self.assertIsInstance(mock, WithNonFields)
11051109
with self.assertRaisesRegex(AttributeError, msg):
11061110
mock.a
11071111
with self.assertRaisesRegex(AttributeError, msg):
11081112
mock.b
11091113

1114+
def test_dataclass_special_attrs(self):
1115+
@dataclass
1116+
class Description:
1117+
name: str
1118+
1119+
for mock in [
1120+
create_autospec(Description, instance=True),
1121+
create_autospec(Description(1)),
1122+
]:
1123+
with self.subTest(mock=mock):
1124+
self.assertIsInstance(mock, Description)
1125+
self.assertIs(mock.__class__, Description)
1126+
self.assertIsInstance(mock.__dataclass_fields__, MagicMock)
1127+
self.assertIsInstance(mock.__dataclass_params__, MagicMock)
1128+
self.assertIsInstance(mock.__match_args__, MagicMock)
1129+
self.assertIsInstance(mock.__hash__, MagicMock)
1130+
11101131
class TestCallList(unittest.TestCase):
11111132

11121133
def test_args_list_contains_call_list(self):

Lib/unittest/mock.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,11 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
569569
__dict__['_mock_methods'] = spec
570570
__dict__['_spec_asyncs'] = _spec_asyncs
571571

572+
def _mock_extend_spec_methods(self, spec_methods):
573+
methods = self.__dict__.get('_mock_methods') or []
574+
methods.extend(spec_methods)
575+
self.__dict__['_mock_methods'] = methods
576+
572577
def __get_return_value(self):
573578
ret = self._mock_return_value
574579
if self._mock_delegate is not None:
@@ -2766,14 +2771,16 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
27662771
raise InvalidSpecError(f'Cannot autospec a Mock object. '
27672772
f'[object={spec!r}]')
27682773
is_async_func = _is_async_func(spec)
2774+
_kwargs = {'spec': spec}
27692775

27702776
entries = [(entry, _missing) for entry in dir(spec)]
27712777
if is_type and instance and is_dataclass(spec):
2778+
is_dataclass_spec = True
27722779
dataclass_fields = fields(spec)
27732780
entries.extend((f.name, f.type) for f in dataclass_fields)
2774-
_kwargs = {'spec': [f.name for f in dataclass_fields]}
2781+
dataclass_spec_list = [f.name for f in dataclass_fields]
27752782
else:
2776-
_kwargs = {'spec': spec}
2783+
is_dataclass_spec = False
27772784

27782785
if spec_set:
27792786
_kwargs = {'spec_set': spec}
@@ -2810,6 +2817,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
28102817

28112818
mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name,
28122819
name=_name, **_kwargs)
2820+
if is_dataclass_spec:
2821+
mock._mock_extend_spec_methods(dataclass_spec_list)
28132822

28142823
if isinstance(spec, FunctionTypes):
28152824
# should only happen at the top level because we don't
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`unittest.mock.Mock` generation on :func:`dataclasses.dataclass`
2+
objects. Now all special attributes are set as it was before :gh:`124429`.

0 commit comments

Comments
 (0)