[3.11] gh-102978: Fix mock.patch function signatures for class and staticmethod decorators (GH-103228) (#103499)

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 59e0de4903)

Co-authored-by: Tomas R <tomas.roun8@gmail.com>
This commit is contained in:
Miss Islington (bot) 2023-05-22 03:47:12 -07:00 committed by GitHub
parent 1692a16a25
commit e95ca78fab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 0 deletions

View file

@ -98,6 +98,12 @@ def _get_signature_object(func, as_instance, eat_self):
func = func.__init__
# Skip the `self` argument in __init__
eat_self = True
elif isinstance(func, (classmethod, staticmethod)):
if isinstance(func, classmethod):
# Skip the `cls` argument of a class method
eat_self = True
# Use the original decorated method to extract the correct function signature
func = func.__func__
elif not isinstance(func, FunctionTypes):
# If we really want to model an instance of the passed type,
# __call__ should be looked up, not __init__.

View file

@ -952,6 +952,24 @@ class SpecSignatureTest(unittest.TestCase):
self.assertFalse(hasattr(autospec, '__name__'))
def test_autospec_signature_staticmethod(self):
class Foo:
@staticmethod
def static_method(a, b=10, *, c): pass
mock = create_autospec(Foo.__dict__['static_method'])
self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock))
def test_autospec_signature_classmethod(self):
class Foo:
@classmethod
def class_method(cls, a, b=10, *, c): pass
mock = create_autospec(Foo.__dict__['class_method'])
self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock))
def test_spec_inspect_signature(self):
def myfunc(x, y): pass

View file

@ -996,6 +996,36 @@ class PatchTest(unittest.TestCase):
method.assert_called_once_with()
def test_autospec_staticmethod_signature(self):
# Patched methods which are decorated with @staticmethod should have the same signature
class Foo:
@staticmethod
def static_method(a, b=10, *, c): pass
Foo.static_method(1, 2, c=3)
with patch.object(Foo, 'static_method', autospec=True) as method:
method(1, 2, c=3)
self.assertRaises(TypeError, method)
self.assertRaises(TypeError, method, 1)
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
def test_autospec_classmethod_signature(self):
# Patched methods which are decorated with @classmethod should have the same signature
class Foo:
@classmethod
def class_method(cls, a, b=10, *, c): pass
Foo.class_method(1, 2, c=3)
with patch.object(Foo, 'class_method', autospec=True) as method:
method(1, 2, c=3)
self.assertRaises(TypeError, method)
self.assertRaises(TypeError, method, 1)
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
def test_autospec_with_new(self):
patcher = patch('%s.function' % __name__, new=3, autospec=True)
self.assertRaises(TypeError, patcher.start)

View file

@ -1533,6 +1533,7 @@ Hugo van Rossum
Saskia van Rossum
Robin Roth
Clement Rouault
Tomas Roun
Donald Wallace Rouse II
Liam Routt
Todd Rovito

View file

@ -0,0 +1,3 @@
Fixes :func:`unittest.mock.patch` not enforcing function signatures for methods
decorated with ``@classmethod`` or ``@staticmethod`` when patch is called with
``autospec=True``.