2

According to the mock guide:

Auto-speccing creates mock objects that have the same attributes and methods as the objects they are replacing, and any functions and methods (including constructors) have the same call signature as the real object.

But that doesn't seem to be true. Stdlib inspect still sees a generic *args, **kwargs signature on the mock:

>>> from unittest.mock import patch
>>> def foo(arg1, arg2, arg3):
...     pass
...     
>>> print(*inspect.signature(foo).parameters)
arg1 arg2 arg3
>>> with patch("__main__.foo", autospec=True) as mock:
...     print(*inspect.signature(mock).parameters)
...     
args kwargs

The autospec does work, in that mock(1,2,3,4) will correctly raise TypeError: too many positional arguments, but it seems this is implemented via some code deeper in the call stack. It's not done via the call signature.

In code where you actually rely on the signature itself (and need the correct signature preserved when mocking in tests), how to autospec a mock in a way that correctly preserves signature?

wim
  • 338,267
  • 99
  • 616
  • 750
  • Is `mock.assert_called_with` the manual way of doing this check? (i.e. not autospec'ing, but checking the signature) – JacobIRR Jan 08 '19 at 21:10

1 Answers1

3

This was actually considered a bug in Python, and has been fixed in Python 3.8. A patch was also backported to Python 3.7.3. The relevant issue and pull request:

I had a similar issue, testing some code that inspects the signature of a callable being mocked. I solved it by setting the __signature__ attribute of the mock to the original one:

from inspect import signature
from unittest.mock import patch

def foo(a, b, c):
    pass

foo_sig = signature(foo)  # Need to cache the signature before mocking foo

with patch("__main__.foo") as mock:
    mock.__signature__ = foo_sig
    print(signature(mock))  # Prints '(a, b, c)'

This works because the signature() function follows the __signature__ attribute before trying other methods, as stated in PEP 362:

If the object has a __signature__ attribute and if it is not None - return it

Unfortunately this is not mentioned in the documentation for the signature() function or the inspect module.

wim
  • 338,267
  • 99
  • 616
  • 750
Macfli
  • 160
  • 1
  • 12
  • This issue has been fixed upstream now. If you've upgraded past Python 3.7.3+ recently and are still setting the `__signature__` manually I think you can remove that from code (have edited your answer directly to mention this at top). – wim May 06 '20 at 06:02