bpo-21478: Record calls to parent when autospecced objects are used as child with attach_mock (GH 14688)

* Clear name and parent of mock in autospecced objects used with attach_mock

* Add NEWS entry

* Fix reversed order of comparison

* Test child and standalone function calls

* Use a helper function extracting mock to avoid code duplication and refactor tests.
This commit is contained in:
Xtreak 2019-07-22 13:08:22 +05:30 committed by Chris Withers
parent b530a4460b
commit 7397cda997
3 changed files with 55 additions and 11 deletions

View File

@ -72,6 +72,15 @@ def _is_exception(obj):
) )
def _extract_mock(obj):
# Autospecced functions will return a FunctionType with "mock" attribute
# which is the actual mock object that needs to be used.
if isinstance(obj, FunctionTypes) and hasattr(obj, 'mock'):
return obj.mock
else:
return obj
def _get_signature_object(func, as_instance, eat_self): def _get_signature_object(func, as_instance, eat_self):
""" """
Given an arbitrary, possibly callable object, try to create a suitable Given an arbitrary, possibly callable object, try to create a suitable
@ -346,13 +355,7 @@ class _CallList(list):
def _check_and_set_parent(parent, value, name, new_name): def _check_and_set_parent(parent, value, name, new_name):
# function passed to create_autospec will have mock value = _extract_mock(value)
# attribute attached to which parent must be set
if isinstance(value, FunctionTypes):
try:
value = value.mock
except AttributeError:
pass
if not _is_instance_mock(value): if not _is_instance_mock(value):
return False return False
@ -467,10 +470,12 @@ class NonCallableMock(Base):
Attach a mock as an attribute of this one, replacing its name and Attach a mock as an attribute of this one, replacing its name and
parent. Calls to the attached mock will be recorded in the parent. Calls to the attached mock will be recorded in the
`method_calls` and `mock_calls` attributes of this one.""" `method_calls` and `mock_calls` attributes of this one."""
mock._mock_parent = None inner_mock = _extract_mock(mock)
mock._mock_new_parent = None
mock._mock_name = '' inner_mock._mock_parent = None
mock._mock_new_name = None inner_mock._mock_new_parent = None
inner_mock._mock_name = ''
inner_mock._mock_new_name = None
setattr(self, attribute, mock) setattr(self, attribute, mock)

View File

@ -37,6 +37,9 @@ class Something(object):
def smeth(a, b, c, d=None): pass def smeth(a, b, c, d=None): pass
def something(a): pass
class MockTest(unittest.TestCase): class MockTest(unittest.TestCase):
def test_all(self): def test_all(self):
@ -1808,6 +1811,26 @@ class MockTest(unittest.TestCase):
self.assertEqual(m.mock_calls, call().foo().call_list()) self.assertEqual(m.mock_calls, call().foo().call_list())
def test_attach_mock_patch_autospec(self):
parent = Mock()
with mock.patch(f'{__name__}.something', autospec=True) as mock_func:
self.assertEqual(mock_func.mock._extract_mock_name(), 'something')
parent.attach_mock(mock_func, 'child')
parent.child(1)
something(2)
mock_func(3)
parent_calls = [call.child(1), call.child(2), call.child(3)]
child_calls = [call(1), call(2), call(3)]
self.assertEqual(parent.mock_calls, parent_calls)
self.assertEqual(parent.child.mock_calls, child_calls)
self.assertEqual(something.mock_calls, child_calls)
self.assertEqual(mock_func.mock_calls, child_calls)
self.assertIn('mock.child', repr(parent.child.mock))
self.assertEqual(mock_func.mock._extract_mock_name(), 'mock.child')
def test_attribute_deletion(self): def test_attribute_deletion(self):
for mock in (Mock(), MagicMock(), NonCallableMagicMock(), for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
NonCallableMock()): NonCallableMock()):
@ -1891,6 +1914,20 @@ class MockTest(unittest.TestCase):
self.assertRaises(TypeError, mock.child, 1) self.assertRaises(TypeError, mock.child, 1)
self.assertEqual(mock.mock_calls, [call.child(1, 2)]) self.assertEqual(mock.mock_calls, [call.child(1, 2)])
self.assertIn('mock.child', repr(mock.child.mock))
def test_parent_propagation_with_autospec_attach_mock(self):
def foo(a, b): pass
parent = Mock()
parent.attach_mock(create_autospec(foo, name='bar'), 'child')
parent.child(1, 2)
self.assertRaises(TypeError, parent.child, 1)
self.assertEqual(parent.child.mock_calls, [call.child(1, 2)])
self.assertIn('mock.child', repr(parent.child.mock))
def test_isinstance_under_settrace(self): def test_isinstance_under_settrace(self):
# bpo-36593 : __class__ is not set for a class that has __class__ # bpo-36593 : __class__ is not set for a class that has __class__

View File

@ -0,0 +1,2 @@
Record calls to parent when autospecced object is attached to a mock using
:func:`unittest.mock.attach_mock`. Patch by Karthikeyan Singaravelan.