Every Python test suite eventually hits the question of which test double to instantiate, and the wrong choice produces one of three signature failures: a bare Mock blowing up with AttributeError: __enter__ inside a with block, a MagicMock raising TypeError: object MagicMock can't be used in 'await' expression, or an AsyncMock silently wrapping a synchronous call in a coroutine nobody awaits. The three classes form a deliberate hierarchy — MagicMock subclasses Mock, and AsyncMock (added in Python 3.8) subclasses MagicMock — each adding support for one more protocol. This guide gives a decision procedure tied to the exact protocol the code under test relies on, so you pick the class that prevents the failure rather than discovering it in CI.
Prerequisites
- Python 3.8+ for
AsyncMock; on 3.7 and earlier you hand-roll awaitable doubles. Examples target 3.11. unittest.mockfrom the standard library.- Working knowledge of context managers, the iterator protocol, and
async/await. The Deep Dive into unittest.mock covers the shared internals.
Solution
The decision is mechanical: match the class to the protocol the code exercises. Use the lowest layer that still works.
Walk it in one direction only. Does the code under test await the double, or does it call an async def on the dependency? If yes, you need AsyncMock. If no, does it drive the double through a dunder protocol — with, async with, for, in, len(), [], or an operator? If yes, you need MagicMock. Otherwise a Mock with a spec is the leanest correct choice. The one shortcut that collapses all three questions is to derive the double from the real object with autospec and let it pick per attribute.
import asyncio
from unittest.mock import Mock, MagicMock, AsyncMock, create_autospec
# --- 1. Mock: plain calls and attributes. Add spec= for a strict contract. ---
class Repo:
def get(self, key: str) -> str: ...
repo = Mock(spec=Repo) # AttributeError on anything not on Repo
repo.get.return_value = "row"
assert repo.get("k") == "row"
repo.get.assert_called_once_with("k")
# --- 2. MagicMock: needed for dunder protocols (with / for / len / []). ---
conn = MagicMock() # __enter__/__exit__ pre-wired
with conn as session: # a bare Mock() raises AttributeError here
session.execute("SELECT 1")
conn.__enter__.assert_called_once()
items = MagicMock()
items.__iter__.return_value = iter([1, 2, 3])
assert list(items) == [1, 2, 3] # iteration works because __iter__ exists
# --- 3. AsyncMock: required when the double is awaited (Python 3.8+). ---
class Client:
async def fetch(self, url: str) -> dict: ...
async def use(client: Client) -> dict:
return await client.fetch("/data") # the code under test awaits here
async def main() -> None:
client = AsyncMock(spec=Client) # call returns an awaitable
client.fetch.return_value = {"ok": True}
result = await use(client) # MagicMock here -> TypeError on await
assert result == {"ok": True}
client.fetch.assert_awaited_once_with("/data") # await-aware assertion
asyncio.run(main())
# --- 4. Let autospec choose per attribute: sync stays Mock/MagicMock,
# coroutine functions become AsyncMock automatically. ---
class Mixed:
def sync_op(self) -> int: ...
async def async_op(self) -> int: ...
m = create_autospec(Mixed)
assert not asyncio.iscoroutinefunction(m.sync_op)
assert asyncio.iscoroutinefunction(m.async_op) # picked AsyncMock for you
Why this works
The three classes are a subclass chain where each adds capability: Mock records calls and auto-creates child attributes, MagicMock additionally pre-configures the magic methods that protocols like with, for, and len() invoke, and AsyncMock overrides __call__ to return a coroutine so await mock() resolves and assert_awaited* helpers track it. MagicMock does not configure every dunder — it deliberately skips a handful (__del__, __getattr__, __setattr__, and the pickling hooks) whose auto-configuration would break normal object behaviour — but every protocol dunder you would exercise in a test is present. In Python 3.8+ MagicMock also detects the async magic methods: __aenter__, __aexit__, __aiter__, and __anext__ are backed by AsyncMock instances, so async with and async for work on a plain MagicMock even though a direct call does not return an awaitable.
Because autospec strict mocking inspects each attribute with asyncio.iscoroutinefunction, create_autospec and autospec=True substitute the correct class per member against the real signature — which is why deriving the double from the real object is the safest default in mixed sync/async code. It removes the choice entirely: you never name Mock, MagicMock, or AsyncMock, and the double stays correct when the dependency later turns a def into an async def.
Edge cases and failure modes
- Awaiting a MagicMock raises
TypeError.await magic_mock()fails because the call returns a plainMagicMock, not an awaitable. Switch the double — or the specific attribute — toAsyncMock. AsyncMockfor a sync function over-wraps the call. Calling a sync dependency backed byAsyncMockreturns an unawaited coroutine; the real call site never awaits it, so the value is lost and you get a "coroutine was never awaited" warning. Mirror the real signature, ideally via autospec. This overlaps with debugging tracing "coroutine was never awaited" warnings.- A bare
Mockcannot enter awithblock or be iterated. It has no__enter__/__iter__. UseMagicMock, or manually attach the dunder methods if you specifically wantMock's leaner surface. async withandasync forneed the async dunders, notAsyncMockon the object itself. An async context manager is entered via__aenter__/__aexit__, and an async iterator via__aiter__/__anext__. A plainMagicMockalready backs those four withAsyncMock, soasync with pool.acquire() as conn:works ifacquire()returns aMagicMock. Configure the entered value withcm.__aenter__.return_value = conn. Reaching forAsyncMockon the object gives you an awaitable call but still no__aenter__unless you spec against the real async context manager.return_valueis shared across every call. All three classes replay one configuredreturn_valuefor every invocation regardless of arguments. When a test needs per-call or per-argument behaviour, move toside_effectwith a list or callable; the interaction between the two is where subtle failures hide, covered in resolving side_effect and return_value conflicts.specdoes not change the call's await behaviour.Mock(spec=AsyncClient)still returns sync values;specconstrains the attribute surface but not the call protocol. For awaitables you needAsyncMock(or autospec, which selects it).- Mixing assertion families.
assert_called_*andassert_awaited_*are not interchangeable: on anAsyncMock,assert_called_oncepasses once the coroutine is created, whileassert_awaited_oncerequires it to have been awaited. Choose the await-aware variant for coroutine doubles: a coroutine that is created but never awaited passesassert_calledand failsassert_awaited, which is exactly the signal you want when a caller forgets toawait.
Picking the class from the call site
You rarely have to reason about the class hierarchy in the abstract: the call site tells you which one you need. Three questions, asked in order, resolve every case.
Does the code await the collaborator, or call something that returns an awaitable? If yes, the double must be an AsyncMock, or patch must be told to make one — since Python 3.8, patch and create_autospec both detect async def on the target and build the async class automatically, which is why an explicitly constructed Mock() passed as an override is the usual cause of an un-awaited coroutine warning.
Does the code use the object through a dunder protocol — len(), in, with, [], iteration, arithmetic? If yes, you need MagicMock (or autospec, which builds one). A plain Mock raises TypeError on the first protocol use.
Otherwise use Mock, and prefer create_autospec whenever the stand-in has a real class behind it.
In review, state the reason for the class in a comment when it is not obvious from the call site — a future reader cannot tell an intentional MagicMock from a careless one, and the difference decides whether a protocol change fails the suite.
The rule holds for nested doubles too. A MagicMock returns child MagicMocks, and an AsyncMock returns child AsyncMocks only for attributes that were async def on the spec — an unspecced AsyncMock makes every child awaitable, which quietly turns a synchronous helper into a coroutine and moves the failure to a confusing place.
Frequently Asked Questions
When must I use AsyncMock instead of MagicMock? Use AsyncMock whenever the code under test awaits the double. AsyncMock returns an awaitable on call so await mock() resolves; a MagicMock returns a plain value, so awaiting it raises TypeError: object MagicMock can't be used in await expression.
Why does a bare Mock fail inside a with statement? A with statement calls enter and exit, which Mock does not pre-configure. MagicMock pre-wires the common dunder methods, so it works in with, for, and len contexts without manual setup.
Does autospec pick the right mock class automatically? Yes. create_autospec and patch with autospec=True inspect each attribute and substitute AsyncMock for coroutine functions and MagicMock or Mock for the rest, so awaitables and dunder protocols are handled to match the real object.
Related guides
- For the two-way comparison and performance notes, read When to Use MagicMock vs Mock in Python.
- Properties need special handling regardless of class — see mocking properties and class attributes with autospec.
- To bind any of these to a real interface, apply autospec strict mocking.
- When a mock feels too brittle for stateful behaviour, weigh injecting fakes vs mocks in constructors.
← Back to Deep Dive into unittest.mock