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.
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. Because create_autospec and autospec=True inspect each attribute with asyncio.iscoroutinefunction, they substitute the correct class per member against the real signature — which is why autospec is the safest default in mixed sync/async code.
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. 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. When configuring return values across these, the rules in resolving side_effect and return_value conflicts apply unchanged.
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