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