Skip to content

Commit 64e3218

Browse files
tomasr8miss-islington
authored andcommitted
pythongh-102978: Fix mock.patch function signatures for class and staticmethod decorators (pythonGH-103228)
Fixes unittest.mock.patch not enforcing function signatures for methods decorated with @classmethod or @staticmethod when patch is called with autospec=True. (cherry picked from commit 59e0de4) Co-authored-by: Tomas R <[email protected]>
1 parent e643412 commit 64e3218

File tree

5 files changed

+58
-0
lines changed

5 files changed

+58
-0
lines changed

Lib/unittest/mock.py

+6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ def _get_signature_object(func, as_instance, eat_self):
9898
func = func.__init__
9999
# Skip the `self` argument in __init__
100100
eat_self = True
101+
elif isinstance(func, (classmethod, staticmethod)):
102+
if isinstance(func, classmethod):
103+
# Skip the `cls` argument of a class method
104+
eat_self = True
105+
# Use the original decorated method to extract the correct function signature
106+
func = func.__func__
101107
elif not isinstance(func, FunctionTypes):
102108
# If we really want to model an instance of the passed type,
103109
# __call__ should be looked up, not __init__.

Lib/unittest/test/testmock/testhelpers.py

+18
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,24 @@ def __getattr__(self, attribute):
952952
self.assertFalse(hasattr(autospec, '__name__'))
953953

954954

955+
def test_autospec_signature_staticmethod(self):
956+
class Foo:
957+
@staticmethod
958+
def static_method(a, b=10, *, c): pass
959+
960+
mock = create_autospec(Foo.__dict__['static_method'])
961+
self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock))
962+
963+
964+
def test_autospec_signature_classmethod(self):
965+
class Foo:
966+
@classmethod
967+
def class_method(cls, a, b=10, *, c): pass
968+
969+
mock = create_autospec(Foo.__dict__['class_method'])
970+
self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock))
971+
972+
955973
def test_spec_inspect_signature(self):
956974

957975
def myfunc(x, y): pass

Lib/unittest/test/testmock/testpatch.py

+30
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,36 @@ def test_autospec_classmethod(self):
996996
method.assert_called_once_with()
997997

998998

999+
def test_autospec_staticmethod_signature(self):
1000+
# Patched methods which are decorated with @staticmethod should have the same signature
1001+
class Foo:
1002+
@staticmethod
1003+
def static_method(a, b=10, *, c): pass
1004+
1005+
Foo.static_method(1, 2, c=3)
1006+
1007+
with patch.object(Foo, 'static_method', autospec=True) as method:
1008+
method(1, 2, c=3)
1009+
self.assertRaises(TypeError, method)
1010+
self.assertRaises(TypeError, method, 1)
1011+
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
1012+
1013+
1014+
def test_autospec_classmethod_signature(self):
1015+
# Patched methods which are decorated with @classmethod should have the same signature
1016+
class Foo:
1017+
@classmethod
1018+
def class_method(cls, a, b=10, *, c): pass
1019+
1020+
Foo.class_method(1, 2, c=3)
1021+
1022+
with patch.object(Foo, 'class_method', autospec=True) as method:
1023+
method(1, 2, c=3)
1024+
self.assertRaises(TypeError, method)
1025+
self.assertRaises(TypeError, method, 1)
1026+
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
1027+
1028+
9991029
def test_autospec_with_new(self):
10001030
patcher = patch('%s.function' % __name__, new=3, autospec=True)
10011031
self.assertRaises(TypeError, patcher.start)

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,7 @@ Hugo van Rossum
15331533
Saskia van Rossum
15341534
Robin Roth
15351535
Clement Rouault
1536+
Tomas Roun
15361537
Donald Wallace Rouse II
15371538
Liam Routt
15381539
Todd Rovito
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixes :func:`unittest.mock.patch` not enforcing function signatures for methods
2+
decorated with ``@classmethod`` or ``@staticmethod`` when patch is called with
3+
``autospec=True``.

0 commit comments

Comments
 (0)