Skip to content
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 @@ -62,6 +62,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 @@ -323,13 +332,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 @@ -433,10 +436,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 @@ -39,6 +39,9 @@ def smeth(a, b, c, d=None):
pass


def something(a): pass


class MockTest(unittest.TestCase):

def test_all(self):
Expand Down Expand Up @@ -1765,6 +1768,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 @@ -1849,6 +1872,20 @@ def foo(a, b):

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.