Skip to content

Commit 361b596

Browse files
committed
Fix mock.patch function signatures for class and staticmethod decorators
Fixes unittest.mock.patch not enforcing function signatures for methods decorated with @classmethod or @staticmethod when patch is called with autospec=True.
1 parent 264c00a commit 361b596

File tree

5 files changed

+62
-0
lines changed

5 files changed

+62
-0
lines changed

Lib/test/test_unittest/testmock/testhelpers.py

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

954954

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

957977
def myfunc(x, y): pass

Lib/test/test_unittest/testmock/testpatch.py

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

998998

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

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__.

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,7 @@ Hugo van Rossum
15481548
Saskia van Rossum
15491549
Robin Roth
15501550
Clement Rouault
1551+
Tomas Roun
15511552
Donald Wallace Rouse II
15521553
Liam Routt
15531554
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)