skip to content

AsyncMock and Async Doubles

The double whose call returns an awaitable, the await assertions that are not the call ones, and what patch picks on its own for a coroutine function. Async doubles fail quietly when they are wrong.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

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

open as a page

How do AsyncMock's await assertions differ from call assertions like assert_called_once_with?

level: middleimportance: must knowfreq 55%

basics

~20 s

An AsyncMock keeps two ledgers: call_count records when it was invoked and a coroutine created, await_count records when that coroutine was actually awaited. assert_awaited_once_with checks the await ledger, so it catches a missing await that call assertions happily pass.

open as a page

When does unittest.mock.patch install an AsyncMock instead of a MagicMock?

level: middleimportance: should knowfreq 50%

basics

~10 s

patch inspects the object it replaces: if it is a coroutine function, the replacement is an AsyncMock; anything else becomes a MagicMock. Passing new= or new_callable= switches that detection off entirely.

open as a page

With unittest.mock, how do you double an `async with` resource and assert it was exited?

level: seniorimportance: should knowfreq 40%

basics

~20 s

MagicMock and AsyncMock already expose aenter and aexit as AsyncMocks, so set aenter.return_value to the fake resource and assert aexit was awaited. Match the factory's shape: a synchronous acquire() needs a MagicMock, an awaited one needs an AsyncMock.

open as a page