skip to content

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

level: middleimportance: should knowfreq 50%

answer

  1. It looks at the target, not the name
  2. One question decides it, asked per attribute
  3. Is this object a coroutine function?
  4. inspect.iscoroutinefunction picks AsyncMock
  5. new_callable overrides the detection

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.

solid answer

~40 s

`patch` and `patch.object` look at the target object and ask `inspect.iscoroutinefunction`. An `async def` target is replaced by an `AsyncMock`; everything else by a `MagicMock`. When a class is specified, the classification runs **per attribute**, so a class with `async def save` and `def close` produces a double whose `save` is an `AsyncMock` and whose `close` is a `MagicMock`. `new_callable=` (or `new=`) overrides the detection, which is both the escape hatch and the usual cause of a mismatched double. The detection legitimately says no in cases people expect a yes: a coroutine function wrapped by a **synchronous** decorator is not a coroutine function even with `functools.wraps`, and a sync factory that returns a task or coroutine is not one either — there you keep the `MagicMock` and make its `return_value` awaitable.

code

python · 14 lines
python
from unittest.mock import MagicMock, create_autospec, patch

class Catalogue:
    async def fetch(self, item_id): ...
    def parse(self, raw): ...

with patch.object(Catalogue, "fetch") as fetch, patch.object(Catalogue, "parse") as parse:
    print(type(fetch).__name__, type(parse).__name__)     # AsyncMock MagicMock

double = create_autospec(Catalogue)
print(type(double.fetch).__name__, type(double.parse).__name__)   # AsyncMock MagicMock

with patch.object(Catalogue, "fetch", new_callable=MagicMock) as fetch:
    print(type(fetch).__name__)                           # MagicMock

go deeper

for a junior

Remember the rule in one line: patch replaces an async def target with an AsyncMock and everything else with a MagicMock. You are not expected to know the corner cases yet.

for a middle

Explain the mechanism — the coroutine-function check on the target object, the per-attribute classification when a class is specified, and how new_callable= overrides it. Be able to say why the wrong class fails at the await.

for a senior

Demonstrate judgement about shape: match the double to what the real object is, know that a synchronous decorator hides an async function from the detection, and recognise the silent failure where an unawaited async double still passes call assertions.

for a principal

Own the codebase convention for async seams — where the team patches, whether doubles are specified from the real class so the async split is derived rather than hand-declared, and how that keeps async refactors from quietly invalidating tests.

`unittest.mock.patch` does not guess from the name of the attribute or from the file it lives in. It looks at **the object it is about to replace** and asks `inspect.iscoroutinefunction` (which is what CPython 3.14's `unittest.mock` imports). If the answer is yes, the replacement is an `AsyncMock`; if no, it is a `MagicMock`. `patch.object` and `patch.dict`-adjacent helpers follow the same rule, and `create_autospec` applies it **per attribute**, so a class with `async def save` and `def close` yields a double whose `save` is an `AsyncMock` and whose `close` is a `MagicMock`. That single rule explains almost everything you will meet in practice, including the surprises. ### Why the automatic choice matters Getting the wrong class is not a cosmetic problem. * A `MagicMock` where the code awaits gives a loud `TypeError` at the await — annoying, but at least the test fails. * An `AsyncMock` where the code does **not** await gives a coroutine object that is silently discarded. The call assertions still pass. That is the failure that ships. So knowing which one `patch` installed is part of reading your own test. `type(double).__name__`, `isinstance(double, AsyncMock)` or `inspect.iscoroutinefunction(double)` (which returns `True` for an `AsyncMock` instance) all answer the question in one line. ### The overrides Two `patch` arguments switch the detection off entirely. * **`new=`** installs the exact object you pass. No detection at all — you own the shape. * **`new_callable=`** names the class to instantiate. `new_callable=AsyncMock` forces an async double onto a target that is not a coroutine function; `new_callable=MagicMock` forces a synchronous double onto one that is. `new_callable=AsyncMock` is the escape hatch for the common real case below, and `new_callable=MagicMock` on an async target is almost always a mistake worth challenging in review. ### Where the detection legitimately says "no" Three shapes routinely fool people who expect an `AsyncMock` and get a `MagicMock`: 1. **A decorated coroutine function.** If a decorator returns a plain synchronous `wrapper`, the object bound in the module namespace *is* that synchronous wrapper, and `inspect.iscoroutinefunction` reports `False` — `functools.wraps` copies the name, the docstring and `__wrapped__`, but it does not make a sync function a coroutine function. `patch` therefore installs a `MagicMock`, and the test blows up at the first await. The fixes are `new_callable=AsyncMock`, or fixing the decorator so the wrapper is itself `async def`. 2. **A synchronous factory that returns an awaitable.** A method that returns a task or a coroutine without being `async def` is correctly doubled by a `MagicMock`; you then have to make its `return_value` awaitable yourself, because the code awaits the *result* of the call, not the call. 3. **A callable object.** An instance whose `__call__` is `async def` is not itself a coroutine function, so a plain instance attribute patched over it will not be detected as async. ### Doing this deliberately The habit that survives review is: decide what the *real* object is — coroutine function, sync function returning an awaitable, async context manager factory — and then either let `patch` detect it or state the class explicitly with `new_callable=`. Where the module under test mixes both kinds of call, patching the whole class rather than each function keeps the async/sync split consistent, because the per-attribute detection does that classification for you. ### A checklist for the three shapes Written as configuration, the decision collapses to three lines. For `await client.fetch(x)` where `fetch` is `async def`, let `patch` do its work and configure `double.return_value`. For `await client.make_task(x)` where `make_task` is a synchronous factory, keep the `MagicMock` and give it a `return_value` that is awaitable. For `async with client.session() as s`, keep the `MagicMock` and configure `client.session.return_value.__aenter__.return_value`. In each case the question you answer is "what does the production call site await?", and the answer determines the class, not the surrounding code's asynchrony. ### Version notes Automatic `AsyncMock` selection, and the per-attribute async detection used when specifying a class, both arrived in Python 3.8 alongside `AsyncMock` itself. Python 3.14's `unittest.mock` performs the check with `inspect.iscoroutinefunction`; note that `asyncio.iscoroutinefunction` is deprecated in 3.14, so write new detection code against the `inspect` version.

  • Your target is `async def` but patch handed you a MagicMock. What is the most likely cause?
    A decorator. If the decorator returns a plain synchronous wrapper, the object bound in the module is that wrapper, and `inspect.iscoroutinefunction` reports False — `functools.wraps` copies metadata but does not make a sync function a coroutine function. Either make the wrapper `async def` or force the double with `new_callable=AsyncMock`.
  • How do you double a synchronous function whose result the code under test awaits?
    Leave it as the MagicMock patch gives you and make its `return_value` awaitable, because the code awaits the result of the call rather than the call. Forcing `new_callable=AsyncMock` there is wrong: the call site does not await the call itself, so the mock's coroutine would be dropped unawaited.
  • How can you check which class patch actually installed?
    Inspect the double: `type(double).__name__`, `isinstance(double, AsyncMock)`, or `inspect.iscoroutinefunction(double)`, which returns True for an AsyncMock instance because the class advertises itself as a coroutine function. Doing this once when a test fails at an await saves a long hunt.

saying these in an interview costs you the question

  • Thinks patch decides from the module or attribute name
  • Believes every mock inside an async test is an AsyncMock
  • Forces new_callable=MagicMock onto an awaited target
  • Assumes functools.wraps preserves coroutine-function status
  • Expects a sync factory returning an awaitable to become AsyncMock

context