mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									b530a4460b
								
							
						
					
					
						commit
						7397cda997
					
				
					 3 changed files with 55 additions and 11 deletions
				
			
		|  | @ -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 | ||||
|  | @ -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 | ||||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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): | ||||
|  | @ -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()): | ||||
|  | @ -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__ | ||||
|  |  | |||
|  | @ -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. | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Xtreak
						Xtreak