New submission from Carl Meyer:

When constructing call-matchers to match expected vs actual calls, if 
`spec=True` was used when patching a function, mock attempts to bind the 
recorded (and expected) call args to the function signature. But if a method 
was mocked, the signature includes `self` and the recorded call args don't. 
This can easily lead to a `TypeError`:

```
from unittest.mock import patch

class Foo:
    def bar(self, x):
        return x

with patch.object(Foo, 'bar', spec=True) as mock_bar:
    f = Foo()
    f.bar(7)

mock_bar.assert_called_once_with(7)

```

The above code worked in mock 1.0, but fails in Python 3.5 and 3.6 tip with 
this error:

```
TypeError: missing a required argument: 'x'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "../mock-method.example.py", line 11, in <module>
    mock_bar.assert_called_once_with(7)
  File "/home/carljm/projects/python/cpython/Lib/unittest/mock.py", line 203, 
in assert_called_once_with
    return mock.assert_called_once_with(*args, **kwargs)
  File "/home/carljm/projects/python/cpython/Lib/unittest/mock.py", line 822, 
in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
  File "/home/carljm/projects/python/cpython/Lib/unittest/mock.py", line 811, 
in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: bar(7)
Actual call: bar(<__main__.Foo object at 0x7fdca80b7550>, 7)
```
```

If you try to pass in the instance as an expected call arg, the error goes away 
but it just fails to match:

```
AssertionError: Expected call: bar(<__main__.Foo object at 0x7f5cbab35fd0>, 7)
Actual call: bar(7)
```

So AFAICT there is no way to successfully use `spec=True` when patching a 
method of a class.

Oddly, using `autospec=True` instead of `spec=True` _does_ record the instance 
as an argument in the recorded call args, meaning that you have to pass it in 
as an argument to e.g. `assert_called_with`. But in many (most?) cases where 
you're patching a method of a class, your test doesn't have access to the 
instance, elsewise you'd likely just patch the instance instead of the class in 
the first place.

I don't see a good reason why `autospec=True` and `spec=True` should differ in 
this way (if both options are needed, there should be a separate flag to 
control that behavior; it doesn't seem related to the documented differences 
between autospec and spec). I do think a) there needs to be some way to record 
call args to a method and assert against those call args without needing the 
instance (or resorting to manual assertions against a sliced `call_args`), and 
b) there should be some way to successfully use `spec=True` when patching a 
method of a class.

----------
components: Library (Lib)
files: mock-method.example.py
messages: 272209
nosy: carljm
priority: normal
severity: normal
status: open
title: call-matcher breaks if a method is mocked with spec=True
versions: Python 3.5, Python 3.6
Added file: http://bugs.python.org/file44054/mock-method.example.py

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue27715>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to