Skip to content

Commit 6595272

Browse files
committed
determine spec shape only at mock construction time
1 parent 1b6241a commit 6595272

File tree

2 files changed

+17
-7
lines changed

2 files changed

+17
-7
lines changed

Diff for: Lib/test/test_unittest/testmock/testasync.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,15 @@ def test_spec_normal_methods_on_class_with_mock(self):
303303
def test_spec_async_attributes_instance(self):
304304
async_instance = AsyncClass()
305305
async_instance.async_func_attr = async_func
306+
async_instance.later_async_func_attr = normal_func
307+
308+
mock_async_instance = Mock(spec_set=async_instance)
309+
310+
async_instance.later_async_func_attr = async_func
306311

307-
mock_async_instance = Mock(async_instance)
308312
self.assertIsInstance(mock_async_instance.async_func_attr, AsyncMock)
313+
# only the shape of the spec at the time of mock construction matters
314+
self.assertNotIsInstance(mock_async_instance.later_async_func_attr, AsyncMock)
309315

310316
def test_spec_mock_type_kw(self):
311317
def inner_test(mock_type):

Diff for: Lib/unittest/mock.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,9 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
506506

507507
_spec_class = None
508508
_spec_signature = None
509-
_spec_obj = None
509+
_spec_asyncs = []
510510

511511
if spec is not None and not _is_list(spec):
512-
_spec_obj = spec
513512
if isinstance(spec, type):
514513
_spec_class = spec
515514
else:
@@ -518,14 +517,20 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
518517
_spec_as_instance, _eat_self)
519518
_spec_signature = res and res[1]
520519

521-
spec = dir(spec)
520+
spec_list = dir(spec)
521+
522+
for attr in spec_list:
523+
if iscoroutinefunction(getattr(spec, attr, None)):
524+
_spec_asyncs.append(attr)
525+
526+
spec = spec_list
522527

523528
__dict__ = self.__dict__
524529
__dict__['_spec_class'] = _spec_class
525-
__dict__['_spec_obj'] = _spec_obj
526530
__dict__['_spec_set'] = spec_set
527531
__dict__['_spec_signature'] = _spec_signature
528532
__dict__['_mock_methods'] = spec
533+
__dict__['_spec_asyncs'] = _spec_asyncs
529534

530535
def __get_return_value(self):
531536
ret = self._mock_return_value
@@ -1015,8 +1020,7 @@ def _get_child_mock(self, /, **kw):
10151020
For non-callable mocks the callable variant will be used (rather than
10161021
any custom subclass)."""
10171022
_new_name = kw.get("_new_name")
1018-
_spec_val = getattr(self.__dict__["_spec_obj"], _new_name, None)
1019-
if _spec_val is not None and asyncio.iscoroutinefunction(_spec_val):
1023+
if _new_name in self.__dict__['_spec_asyncs']:
10201024
return AsyncMock(**kw)
10211025

10221026
if self._mock_sealed:

0 commit comments

Comments
 (0)