Isolation & Contracts

Mock vs MagicMock vs AsyncMock — When to Use Each

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.mock from the standard library.
  • Working knowledge of context managers, the iterator protocol, and async/await. The Deep Dive into unittest.mock covers the shared internals.
Mock, MagicMock and AsyncMock feature matrix A comparison matrix showing which capabilities Mock, MagicMock and AsyncMock support across attribute tracking, dunder protocols and awaitable returns. Which double supports what Mock MagicMock AsyncMock call & attr tracking yes yes yes dunder protocols no yes yes awaitable on call no no yes available since always always 3.8+ Each class adds one protocol layer to its parent.
Mock tracks calls; MagicMock adds dunder protocol support; AsyncMock (3.8+) adds awaitable returns. Pick the lowest layer that covers what the code under test exercises.

Solution

The decision is mechanical: match the class to the protocol the code exercises. Use the lowest layer that still works.

Python
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 plain MagicMock, not an awaitable. Switch the double — or the specific attribute — to AsyncMock.
  • AsyncMock for a sync function over-wraps the call. Calling a sync dependency backed by AsyncMock returns 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 Mock cannot enter a with block or be iterated. It has no __enter__/__iter__. Use MagicMock, or manually attach the dunder methods if you specifically want Mock's leaner surface.
  • spec does not change the call's await behaviour. Mock(spec=AsyncClient) still returns sync values; spec constrains the attribute surface but not the call protocol. For awaitables you need AsyncMock (or autospec, which selects it).
  • Mixing assertion families. assert_called_* and assert_awaited_* are not interchangeable: on an AsyncMock, assert_called_once passes once the coroutine is created, while assert_awaited_once requires 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.

← Back to Deep Dive into unittest.mock