]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-135368: Fix mocks on dataclass specs with `instance=True` (#135421)
authorsobolevn <mail@sobolevn.me>
Sat, 14 Jun 2025 08:46:43 +0000 (11:46 +0300)
committerGitHub <noreply@github.com>
Sat, 14 Jun 2025 08:46:43 +0000 (09:46 +0100)
* gh-135368: Fix mocks on dataclass specs with `instance=True`

* Extend dataclass mock_methods

---------

Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Lib/test/test_unittest/testmock/testhelpers.py
Lib/unittest/mock.py
Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst [new file with mode: 0644]

index d1e48bde982040e1ea55256dab8b521c3e8955c8..0e82c723ec3eaa2495f7ee929eed8a20b4f75a42 100644 (file)
@@ -1050,6 +1050,7 @@ class SpecSignatureTest(unittest.TestCase):
             create_autospec(WithPostInit()),
         ]:
             with self.subTest(mock=mock):
+                self.assertIsInstance(mock, WithPostInit)
                 self.assertIsInstance(mock.a, int)
                 self.assertIsInstance(mock.b, int)
 
@@ -1072,6 +1073,7 @@ class SpecSignatureTest(unittest.TestCase):
             create_autospec(WithDefault(1)),
         ]:
             with self.subTest(mock=mock):
+                self.assertIsInstance(mock, WithDefault)
                 self.assertIsInstance(mock.a, int)
                 self.assertIsInstance(mock.b, int)
 
@@ -1087,6 +1089,7 @@ class SpecSignatureTest(unittest.TestCase):
             create_autospec(WithMethod(1)),
         ]:
             with self.subTest(mock=mock):
+                self.assertIsInstance(mock, WithMethod)
                 self.assertIsInstance(mock.a, int)
                 mock.b.assert_not_called()
 
@@ -1102,11 +1105,29 @@ class SpecSignatureTest(unittest.TestCase):
             create_autospec(WithNonFields(1)),
         ]:
             with self.subTest(mock=mock):
+                self.assertIsInstance(mock, WithNonFields)
                 with self.assertRaisesRegex(AttributeError, msg):
                     mock.a
                 with self.assertRaisesRegex(AttributeError, msg):
                     mock.b
 
+    def test_dataclass_special_attrs(self):
+        @dataclass
+        class Description:
+            name: str
+
+        for mock in [
+            create_autospec(Description, instance=True),
+            create_autospec(Description(1)),
+        ]:
+            with self.subTest(mock=mock):
+                self.assertIsInstance(mock, Description)
+                self.assertIs(mock.__class__, Description)
+                self.assertIsInstance(mock.__dataclass_fields__, MagicMock)
+                self.assertIsInstance(mock.__dataclass_params__, MagicMock)
+                self.assertIsInstance(mock.__match_args__, MagicMock)
+                self.assertIsInstance(mock.__hash__, MagicMock)
+
 class TestCallList(unittest.TestCase):
 
     def test_args_list_contains_call_list(self):
index 55cb4b1f6aff90147e5f1ed3cc7ede23620cb1ca..e370aa48b7c70309975b335197b6aee0a28cf696 100644 (file)
@@ -569,6 +569,11 @@ class NonCallableMock(Base):
         __dict__['_mock_methods'] = spec
         __dict__['_spec_asyncs'] = _spec_asyncs
 
+    def _mock_extend_spec_methods(self, spec_methods):
+        methods = self.__dict__.get('_mock_methods') or []
+        methods.extend(spec_methods)
+        self.__dict__['_mock_methods'] = methods
+
     def __get_return_value(self):
         ret = self._mock_return_value
         if self._mock_delegate is not None:
@@ -2766,14 +2771,16 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
         raise InvalidSpecError(f'Cannot autospec a Mock object. '
                                f'[object={spec!r}]')
     is_async_func = _is_async_func(spec)
+    _kwargs = {'spec': spec}
 
     entries = [(entry, _missing) for entry in dir(spec)]
     if is_type and instance and is_dataclass(spec):
+        is_dataclass_spec = True
         dataclass_fields = fields(spec)
         entries.extend((f.name, f.type) for f in dataclass_fields)
-        _kwargs = {'spec': [f.name for f in dataclass_fields]}
+        dataclass_spec_list = [f.name for f in dataclass_fields]
     else:
-        _kwargs = {'spec': spec}
+        is_dataclass_spec = False
 
     if spec_set:
         _kwargs = {'spec_set': spec}
@@ -2810,6 +2817,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
 
     mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name,
                  name=_name, **_kwargs)
+    if is_dataclass_spec:
+        mock._mock_extend_spec_methods(dataclass_spec_list)
 
     if isinstance(spec, FunctionTypes):
         # should only happen at the top level because we don't
diff --git a/Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst b/Misc/NEWS.d/next/Library/2025-06-12-10-45-02.gh-issue-135368.OjWVHL.rst
new file mode 100644 (file)
index 0000000..b9973d8
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :class:`unittest.mock.Mock` generation on :func:`dataclasses.dataclass`
+objects. Now all special attributes are set as it was before :gh:`124429`.