Skip to content

Commit

Permalink
gh-94924: support inspect.iscoroutinefunction in `create_autospec(a…
Browse files Browse the repository at this point in the history
…sync_def)` (#94962)

* support inspect.iscoroutinefunction in create_autospec(async_def)

* test create_autospec with inspect.iscoroutine and inspect.iscoroutinefunction

* test when create_autospec functions check their signature

Backports: 9bf8d825a66ea2a76169b917c12c237a6af2ed75
Signed-off-by: Chris Withers <chris@simplistix.co.uk>
  • Loading branch information
graingert authored and cjw296 committed Jul 11, 2023
1 parent 46733c6 commit 6f79656
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 2 deletions.
1 change: 1 addition & 0 deletions NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction`
32 changes: 30 additions & 2 deletions mock/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,33 @@ def checksig(*args, **kwargs):
_setup_func(funcopy, mock, sig)
return funcopy

def _set_async_signature(mock, original, instance=False, is_async_mock=False):
# creates an async function with signature (*args, **kwargs) that delegates to a
# mock. It still does signature checking by calling a lambda with the same
# signature as the original.

skipfirst = isinstance(original, type)
result = _get_signature_object(original, instance, skipfirst)
if result is None:
return mock
func, sig = result
def checksig(*args, **kwargs):
sig.bind(*args, **kwargs)
_copy_func_details(func, checksig)

name = original.__name__
if not name.isidentifier():
name = 'funcopy'
context = {'_checksig_': checksig, 'mock': mock}
src = """async def %s(*args, **kwargs):
_checksig_(*args, **kwargs)
return await mock(*args, **kwargs)""" % name
exec (src, context)
funcopy = context[name]
_setup_func(funcopy, mock, sig)
_setup_async_mock(funcopy)
return funcopy


def _setup_func(funcopy, mock, sig):
funcopy.mock = mock
Expand Down Expand Up @@ -2798,9 +2825,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
if isinstance(spec, FunctionTypes):
# should only happen at the top level because we don't
# recurse for functions
mock = _set_signature(mock, spec)
if is_async_func:
_setup_async_mock(mock)
mock = _set_async_signature(mock, spec)
else:
mock = _set_signature(mock, spec)
else:
_check_signature(spec, mock, is_type, instance)

Expand Down
23 changes: 23 additions & 0 deletions mock/tests/testasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ async def main():
run(main())

self.assertTrue(iscoroutinefunction(spec))
self.assertTrue(inspect.iscoroutinefunction(spec))
self.assertTrue(asyncio.iscoroutine(awaitable))
self.assertTrue(inspect.iscoroutine(awaitable))
self.assertEqual(spec.await_count, 1)
self.assertEqual(spec.await_args, call(1, 2, c=3))
self.assertEqual(spec.await_args_list, [call(1, 2, c=3)])
Expand All @@ -254,6 +256,25 @@ async def main():
with self.assertRaises(AssertionError):
spec.assert_any_await(e=1)

def test_autospec_checks_signature(self):
spec = create_autospec(async_func_args)
# signature is not checked when called
awaitable = spec()
self.assertListEqual(spec.mock_calls, [])

async def main():
await awaitable

# but it is checked when awaited
with self.assertRaises(TypeError):
run(main())

# _checksig_ raises before running or awaiting the mock
self.assertListEqual(spec.mock_calls, [])
self.assertEqual(spec.await_count, 0)
self.assertIsNone(spec.await_args)
self.assertEqual(spec.await_args_list, [])
spec.assert_not_awaited()

def test_patch_with_autospec(self):

Expand All @@ -263,7 +284,9 @@ async def test_async():
self.assertIsInstance(mock_method.mock, AsyncMock)

self.assertTrue(iscoroutinefunction(mock_method))
self.assertTrue(inspect.iscoroutinefunction(mock_method))
self.assertTrue(asyncio.iscoroutine(awaitable))
self.assertTrue(inspect.iscoroutine(awaitable))
self.assertTrue(inspect.isawaitable(awaitable))

# Verify the default values during mock setup
Expand Down

0 comments on commit 6f79656

Please sign in to comment.