This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author mariocj89
Recipients cjw296, guboi72, jekin000, jwdevel, mariocj89, michael.foord, rbcollins, xtreak
Date 2019-03-03.00:57:02
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1551574622.76.0.242753139181.issue26752@roundup.psfhosted.org>
In-reply-to
Content
Quite a tricky bug!

Indeed @xtreak the `_call_matcher` is using the `__init__` signature. I think this is a bug introduced in https://bugs.python.org/issue17015. There is a mix of the fact that spec in Mock also can accept classes (not instances) whilst spec requires the user to say whether what you are passing is a class or an instance. This gets messed up when validating the calls as at validation time it tries to match the signature as done in issue17015 (something that is useful for other cases as outlined in the issue).


You can see how this is clearly a bug with the following reproducer:

```
from unittest.mock import Mock, call
class X(object):
    def __init__(self):pass
    def foo(self, a):pass
x = Mock(spec=X)
x.foo(20)
x.assert_has_calls(x.mock_calls)
```


Using an instance (`X()`) of the class "hides" the issue, as the right signature is used for validation.


I am not sure if there is any easy fix here :/, but it is broken that the validation happens in different ways when a class is used in the spec (when calling it vs when validating it). Things "work" as if you use a spec of a class and then construct it, that passes fine as it does not get validated as attribute lookup and then there is no further validation.

Maybe something like this would work:

```
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -384,8 +384,10 @@ class NonCallableMock(Base):
     def __init__(
             self, spec=None, wraps=None, name=None, spec_set=None,
             parent=None, _spec_state=None, _new_name='', _new_parent=None,
-            _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs
+            _spec_as_instance=None, _eat_self=None, unsafe=False, **kwargs
         ):
+        if _spec_as_instance is None:
+            _spec_as_instance = isinstance(spec, type) # We might need to play with eat_self as well here.
         if _new_parent is None:
             _new_parent = parent

@@ -2205,8 +2207,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
     elif spec is None:
         # None we mock with a normal mock without a spec
         _kwargs = {}
-    if _kwargs and instance:
-        _kwargs['_spec_as_instance'] = True
+    if _kwargs:
+        _kwargs['_spec_as_instance'] = instance
```

Basically, being explicit on auto_spec and inferring it on the creation of Mocks, but that might break some people who (probably badly) rely on the signature of the class.

This issue probably needs some further work.
History
Date User Action Args
2019-03-03 00:57:02mariocj89setrecipients: + mariocj89, rbcollins, cjw296, michael.foord, jekin000, jwdevel, guboi72, xtreak
2019-03-03 00:57:02mariocj89setmessageid: <1551574622.76.0.242753139181.issue26752@roundup.psfhosted.org>
2019-03-03 00:57:02mariocj89linkissue26752 messages
2019-03-03 00:57:02mariocj89create