Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Given an arbitrary, possibly callable object, try to create a suitable
Expand Down Expand Up @@ -346,13 +355,7 @@ def __repr__(self):


def _check_and_set_parent(parent, value, name, new_name):
# function passed to create_autospec will have mock
# attribute attached to which parent must be set
if isinstance(value, FunctionTypes):
try:
value = value.mock
except AttributeError:
pass
value = _extract_mock(value)

if not _is_instance_mock(value):
return False
Expand Down Expand Up @@ -467,10 +470,12 @@ def attach_mock(self, mock, attribute):
Attach a mock as an attribute of this one, replacing its name and
parent. Calls to the attached mock will be recorded in the
`method_calls` and `mock_calls` attributes of this one."""
mock._mock_parent = None
mock._mock_new_parent = None
mock._mock_name = ''
mock._mock_new_name = None
inner_mock = _extract_mock(mock)

inner_mock._mock_parent = None
inner_mock._mock_new_parent = None
inner_mock._mock_name = ''
inner_mock._mock_new_name = None

setattr(self, attribute, mock)

Expand Down
37 changes: 37 additions & 0 deletions Lib/unittest/test/testmock/testmock.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def cmeth(cls, a, b, c, d=None): pass
def smeth(a, b, c, d=None): pass


def something(a): pass


class MockTest(unittest.TestCase):

def test_all(self):
Expand Down Expand Up @@ -1808,6 +1811,26 @@ def test_attach_mock_return_value(self):
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):
for mock in (Mock(), MagicMock(), NonCallableMagicMock(),
NonCallableMock()):
Expand Down Expand Up @@ -1891,6 +1914,20 @@ def foo(a, b): pass

self.assertRaises(TypeError, mock.child, 1)
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):
# bpo-36593 : __class__ is not set for a class that has __class__
Expand Down
Original file line number Diff line number Diff line change
@@ -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.