New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unittest mock create_autospec doesn't correctly replace mocksignature #61387
Comments
Sticking an issue in at Michael's request... Older versions of mock had a helper called mocksignature. >>> from inspect import getargspec
>>> from mock import create_autospec
>>> def myfunc(x, y): pass
...
>>> getargspec(myfunc)
ArgSpec(args=['x', 'y'], varargs=None, keywords=None, defaults=None)
>>> getargspec(create_autospec(myfunc))
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None) mocksignature gets it right: >>> from mock import mocksignature
>>> getargspec(mocksignature(myfunc))
ArgSpec(args=['x', 'y'], varargs=None, keywords=None, defaults=None) |
Just curiosity why such a name change? |
It was a functionality change, not just a name change. |
Is this being worked on or can I try fixing this? My analysis so far is as below :
Attaching a sample patch with tests. Hope I am on the right track and guidance would help. I am changing the version to 3.8 diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index a9c82dcb5d..8cbef0e514 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -103,6 +103,7 @@ def _check_signature(func, mock, skipfirst, instance=False):
sig.bind(*args, **kwargs)
_copy_func_details(func, checksig)
type(mock)._mock_check_sig = checksig
+ type(mock).__signature__ = sig
def _copy_func_details(func, funcopy):
@@ -172,11 +173,11 @@ def _set_signature(mock, original, instance=False):
return mock(*args, **kwargs)""" % name
exec (src, context)
funcopy = context[name]
- _setup_func(funcopy, mock)
+ _setup_func(funcopy, mock, sig)
return funcopy -def _setup_func(funcopy, mock): # can't use isinstance with mocks
@@ -224,6 +225,7 @@ def _setup_func(funcopy, mock):
funcopy.assert_called = assert_called
funcopy.assert_not_called = assert_not_called
funcopy.assert_called_once = assert_called_once
+ funcopy.__signature__ = sig mock._mock_delegate = funcopy Initial set of tests where partial function and class test fails : def test_spec_inspect_signature(self):
def foo(a: int, b: int=10, *, c:int) -> int:
return a b c
mock = create_autospec(foo)
assert inspect.getfullargspec(mock) == inspect.getfullargspec(foo)
self.assertRaises(TypeError, mock, 1)
def test_spec_inspect_signature_partial(self):
def foo(a: int, b: int=10, *, c:int) -> int:
return a b c
import functools
partial_object = functools.partial(foo, 1)
mock = create_autospec(partial_object)
assert inspect.getfullargspec(mock) == inspect.getfullargspec(partial_object) # Fails
self.assertRaises(TypeError, partial_object)
def test_spec_inspect_signature_class(self):
class Bar:
def __init__(self, a: int):
self.a = a
mock = create_autospec(Bar)
assert inspect.getfullargspec(mock) == inspect.getfullargspec(Bar) # Fails since mock signature has no self
self.assertRaises(TypeError, mock)
self._check_someclass_mock(mock) |
xtreak - great to see action on this! First step would be to add a unit test for the failure case I reported. I like the tests you have too, but would be good to see the specific failure case covered too. Beyond that, if we can get all the the new tests passing, then I'd say you're there! |
While working on partials test case failure I found two more cases along the way.
To be honest I don't if my getting hacky at this point I will clean up the code in a couple of days and raise a PR for initial feedback. Current changes are at master...tirkarthi:bpo-17185. I am adding @mariocj89 to the issue. Sample reproducer : from functools import partial, partialmethod
from unittest.mock import create_autospec
import inspect
def foo(a, b):
pass
p = partial(foo, 1)
m = create_autospec(p)
m(1, 2, 3) # passes since signature is set as (*args, **kwargs) the signature of functools.partial constructor. This should throw TypeError under autospec
class A:
def f(self, a, b):
print(a, b)
g = partialmethod(f, 1)
m = create_autospec(A)
m().g(1, 2) # passes since signature is set as (self, b) and self is not skipped in _must_skip thus self=1, b=2. This should throw TypeError under autospec since the valid call is m().g(2) |
Agree that it sounds reasonable to just set For the partials bug, I'll add Pablo as we were speaking today about something similar :) but that might be unrelated (though interesting and worth fixing!) from the issue Chris brought up. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: