Message272209
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. |
|
Date |
User |
Action |
Args |
2016-08-09 02:43:25 | carljm | set | recipients:
+ carljm |
2016-08-09 02:43:25 | carljm | set | messageid: <1470710605.81.0.432461136624.issue27715@psf.upfronthosting.co.za> |
2016-08-09 02:43:25 | carljm | link | issue27715 messages |
2016-08-09 02:43:24 | carljm | create | |
|