skip to content

What does calling a unittest.mock.AsyncMock return, and how does that differ from Mock?

level: juniorimportance: must knowfreq 60%

answer

  1. The call runs none of the real body
  2. The call hands back something unfinished
  3. You must await what the call returned
  4. A coroutine object; await yields return_value
  5. Awaiting a MagicMock raises TypeError

basics

~10 s

Calling an AsyncMock runs nothing and hands back a coroutine object; awaiting that coroutine produces the mock's return_value. A plain Mock returns its return_value immediately, and awaiting a Mock raises TypeError.

solid answer

~40 s

`unittest.mock.AsyncMock` is the double for a coroutine function. Calling it records the call and returns a coroutine object without producing anything; the configured `return_value` is delivered only when that coroutine is awaited, so `await double(1)` gives the configured result while a bare `double(1)` leaves an unawaited coroutine and a `RuntimeWarning` that it was never awaited. `return_value` is a plain value — you do not wrap it in a future. `side_effect` accepts the usual shapes plus an async callable, which the mock awaits. A synchronous `Mock` or `MagicMock` cannot stand in: on Python 3.14 awaiting one raises `TypeError: 'MagicMock' object can't be awaited`. `AsyncMock` subclasses `MagicMock`, so `__aenter__` and `__aexit__` on it are AsyncMocks too, and `inspect.iscoroutinefunction(AsyncMock())` returns `True`.

code

python · 14 lines
python
import asyncio
from unittest.mock import AsyncMock, MagicMock

fetch = AsyncMock(return_value={"id": 7})

async def main():
    fetch()                       # creates a coroutine object, runs nothing
    print(await fetch())          # {'id': 7}
    try:
        await MagicMock()
    except TypeError as exc:
        print(exc)                # 'MagicMock' object can't be awaited

asyncio.run(main())

go deeper

for a junior

Be ready to say in one sentence that calling an AsyncMock gives you a coroutine and that awaiting it gives you return_value. Knowing that a plain Mock cannot be awaited is the other half interviewers listen for.

for a middle

Explain the mechanics: the call records call_count and builds the coroutine, the await records await_count and applies side_effect or return_value. Mention that return_value is a plain value needing no future wrapper.

for a senior

Show the diagnosis habit — treat the never-awaited RuntimeWarning as a real failure signal, turn warnings into errors in CI, and assert on awaits so a missing await in production code cannot slip past a green suite.

for a principal

Own the convention: which doubles a codebase uses for async boundaries, whether unawaited-coroutine warnings are errors in the pipeline, and how the team avoids doubles that are shaped differently from the objects they replace.

`unittest.mock.AsyncMock` is the test double for a **coroutine function** — something defined with `async def`. It exists because a plain `Mock` gets the shape of an async call wrong in a way that either explodes or, worse, passes quietly. ### What a call does, and what an await does Two separate events happen when production code runs `result = await double(item_id)`. 1. **The call.** `double(item_id)` invokes the mock. `AsyncMock.__call__` records the call in `call_count`/`call_args_list` exactly as `Mock` does, and then returns a **coroutine object**. Nothing you configured has been produced yet; no `side_effect` has run. 2. **The await.** Awaiting that coroutine object is what makes the mock produce a value. It records the await in `await_count`/`await_args`/`await_args_list`, applies `side_effect` if one is set, and otherwise returns `return_value`. The practical consequence is that `return_value` is a **plain value**, not something you have to wrap. `AsyncMock(return_value={"id": 7})` makes `await double()` produce `{"id": 7}`. Candidates who learned mocking on Python 3.7 often still wrap results in a future or hand-roll a coroutine function; on 3.8 and later that is not only unnecessary, it is wrong — the await then yields the future object rather than the value inside it. Reach for a real awaitable only when the code under test treats the returned object as a future in its own right, for instance by cancelling it or attaching a done callback. `side_effect` on an `AsyncMock` accepts the same shapes as on `Mock` — an exception instance or class, an iterable of results, a callable — plus one extra: an **async** callable, which the mock awaits and whose result it returns. That is the clean way to compute a per-call answer or to raise only for particular arguments. ### The two ways to get it wrong **Forgetting the await in production code.** If the code under test calls the double but never awaits it, the coroutine object is created, dropped and eventually garbage-collected, and CPython emits `RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited`. That warning is easy to lose in a noisy test log, so it is worth turning warnings into errors in CI, and better still to assert on the mock's `await_count` rather than trusting a warning to surface. This is the single most valuable thing `AsyncMock` gives you: real code that forgets an `await` produces a double that was *called* but never *awaited*, and only an await assertion notices. **Using a synchronous double where a coroutine function is expected.** Awaiting a `Mock` or `MagicMock` raises `TypeError`. On Python 3.14 the message reads `'MagicMock' object can't be awaited`; through 3.13 the same condition read `object MagicMock can't be used in 'await' expression`, so never assert on that string in a test. This failure is loud, which makes it the friendly one — the dangerous case is the reverse, where an async double stands in for something the code never awaits. ### How AsyncMock relates to the rest of the family `AsyncMock` subclasses `MagicMock`, so it inherits the pre-configured magic methods and adds the asynchronous ones: `__aenter__`, `__aexit__` and `__anext__` on any `MagicMock` or `AsyncMock` are themselves `AsyncMock` instances, which is why an `async with` over a `MagicMock` works out of the box. The double also advertises itself convincingly: `inspect.iscoroutinefunction(AsyncMock())` returns `True`, so production code that branches on that check still takes its async path. Conversely, not every async-looking thing needs an `AsyncMock`. A **synchronous** function that returns an awaitable — a factory that hands back a task or a coroutine — is not a coroutine function. Its double should be a `MagicMock` whose `return_value` is awaitable. Matching the double to the real object's shape, rather than to the presence of the word "async" nearby, is the whole skill. ### Reading a failure Three symptoms map cleanly back to this model, and recognising them saves a long hunt. `TypeError: 'MagicMock' object can't be awaited` means a synchronous double sits where a coroutine function belongs. `RuntimeWarning: coroutine ... was never awaited` means an async double was called by code that did not await it — either a real bug in the code under test, or a double that should have been synchronous. And an assertion that reports a mock was never awaited, while the call assertion for the same arguments passes, means the coroutine was created and dropped somewhere in between. ### What AsyncMock does not give you It is a double, not a scheduler. Constructing one needs no running event loop, and it introduces no concurrency of its own: the await resolves immediately, in order, with no suspension point that another task can slip through. That is usually what you want in a unit test, but it means an `AsyncMock` cannot reproduce interleaving bugs — a double that always completes instantly will never show you the window in which two tasks race. Timing-shaped behaviour has to be modelled explicitly, for instance with an async `side_effect` that awaits before returning. ### Version notes `AsyncMock`, the await assertions, and automatic async magic-method support all arrived in Python 3.8. Nothing about that core behaviour changed through 3.14; only the wording of the `TypeError` raised when a non-async mock is awaited was rewritten in 3.14.

  • What does CPython report when the code under test calls your AsyncMock but never awaits it?
    When the orphaned coroutine object is collected, CPython emits `RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited`. It is easy to lose in test output, so turn warnings into errors in CI and, more reliably, assert on the mock's `await_count` instead of trusting a warning to surface.
  • Do you have to wrap an AsyncMock's return_value in a coroutine or a future yourself?
    No. Assign the plain value — `double.return_value = {'id': 7}` — and the await produces it. Wrapping it means the await yields the wrapper object instead of the value. Build a real awaitable only when the code under test treats the result as a future in its own right, for example by cancelling it or attaching a done callback.
  • What extra shape does side_effect accept on an AsyncMock that it does not on Mock?
    An async callable. The mock awaits it and returns its result, which is the clean way to compute a per-call answer or raise only for particular arguments. Exceptions, iterables of results and plain callables behave as they do on a synchronous mock; the awaited callable is the addition.

Calling an AsyncMock is like being handed a sealed order ticket: the ticket records that you asked, but nothing is prepared until you take it to the counter and wait.

saying these in an interview costs you the question

  • Thinks calling an AsyncMock runs the real coroutine body
  • Wraps return_value in a future or coroutine needlessly
  • Believes a plain Mock works inside an await expression
  • Ignores the never-awaited RuntimeWarning in test output
  • Confuses the returned coroutine with the awaited value

context