Skip to content

Commit 60bbe51

Browse files
committed
bpo-32092: consumes self/cls arguments when patching methods
Currently, when patching methods with autospec, their self / cls arguments are not consumed, causing call asserts to fail (they expect an instance / class reference as the first argument). Example: from unittest import mock class Something(object): def foo(self, a, b, c, d): pass patcher = mock.patch.object(Something, 'foo', autospec=True) patcher.start() s = Something() s.foo()
1 parent 13f993d commit 60bbe51

3 files changed

Lines changed: 34 additions & 4 deletions

File tree

‎Lib/unittest/mock.py‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ def _set_signature(mock, original, instance=False):
155155
if not _callable(original):
156156
return
157157

158+
if '_spec_signature' in mock.__dict__:
159+
# mock already has a spec signature. Nothing to do.
160+
return mock
161+
158162
skipfirst = isinstance(original, type)
159163
result = _get_signature_object(original, instance, skipfirst)
160164
if result is None:
@@ -1354,11 +1358,21 @@ def __enter__(self):
13541358
if original is DEFAULT:
13551359
raise TypeError("Can't use 'autospec' with create=True")
13561360
spec_set = bool(spec_set)
1361+
is_class = isinstance(self.target, type)
13571362
if autospec is True:
1358-
autospec = original
1363+
if is_class:
1364+
# if it's a class, "original" could represent a class
1365+
# or static method object (non-callable), instead of the
1366+
# method itself, causing a NonCallableMagicMock to be
1367+
# created.
1368+
autospec = getattr(self.target, self.attribute, original)
1369+
else:
1370+
autospec = original
13591371

1372+
eat_self = _must_skip(self.target, self.attribute, is_class)
13601373
new = create_autospec(autospec, spec_set=spec_set,
1361-
_name=self.attribute, **kwargs)
1374+
_name=self.attribute, _eat_self=eat_self,
1375+
**kwargs)
13621376
elif kwargs:
13631377
# can't set keyword args when we aren't creating the mock
13641378
# XXXX If new is a Mock we could call new.configure_mock(**kwargs)
@@ -2252,9 +2266,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
22522266
new = _SpecState(original, spec_set, mock, entry, instance)
22532267
mock._mock_children[entry] = new
22542268
else:
2269+
# we are looping over a spec's attributes, so mock is the parent
2270+
# of mocked spec attributes.
22552271
parent = mock
2256-
if isinstance(spec, FunctionTypes):
2257-
parent = mock.mock
22582272

22592273
skipfirst = _must_skip(spec, entry, is_type)
22602274
kwargs['_eat_self'] = skipfirst

‎Lib/unittest/test/testmock/testmock.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,21 @@ def test_filter_dir(self):
790790
patcher.stop()
791791

792792

793+
def test_patch_autospec_obj(self):
794+
something = Something()
795+
with patch.multiple(something, meth=DEFAULT, cmeth=DEFAULT,
796+
smeth=DEFAULT, autospec=True):
797+
self._check_autospeced_something(something)
798+
799+
800+
@patch.object(Something, 'smeth', autospec=True)
801+
@patch.object(Something, 'cmeth', autospec=True)
802+
@patch.object(Something, 'meth', autospec=True)
803+
def test_patch_autospec_class(self, mock_meth, mock_cmeth, mock_smeth):
804+
something = Something()
805+
self._check_autospeced_something(something)
806+
807+
793808
def test_configure_mock(self):
794809
mock = Mock(foo='bar')
795810
self.assertEqual(mock.foo, 'bar')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock: Fixes mock.patch self / cls argument consumption when autospec=True.

0 commit comments

Comments
 (0)