Isolation & Contracts

When to Use MagicMock vs Mock in Python

You reach for a test double and hit one of two failure modes: a plain Mock() blows up with TypeError: object does not support the context manager protocol the moment your code does with conn:, or a MagicMock() silently passes a test that should have caught a deleted method because it auto-created the attribute. Choosing between unittest.mock.Mock and unittest.mock.MagicMock is the fix, and the decision rests on one question: does the code under test use Python's dunder protocols against the double? This guide gives you the precise rule and the runnable checks to confirm it.

Prerequisites

  • Python 3.8+ (MagicMock and Mock are unchanged in this respect since 3.3; AsyncMock requires 3.8).
  • unittest.mock from the standard library.
  • Optional: pytest 7.0+ for the fixture-scoping notes.

Solution

MagicMock is a subclass of Mock that pre-configures the common magic (dunder) methods. That is the whole difference. Both classes auto-create child mocks for undefined regular attributes; only MagicMock also answers __enter__, __exit__, __iter__, __len__, __getitem__, __contains__, the comparison and arithmetic operators, and friends. So the rule is: use MagicMock when your code drives the double through a protocol (with, for, len, [], +); use Mock when the double is only called or has plain attributes read. Add spec= to either when you want a typo to raise.

Python
from unittest.mock import Mock, MagicMock

# 1. Plain method call -> Mock is enough.
service = Mock()
service.fetch.return_value = {"id": 1}
assert service.fetch()["id"] == 1            # no dunder protocol involved

# 2. Context manager -> needs MagicMock (Mock raises TypeError on `with`).
conn = MagicMock()
with conn as session:                        # __enter__/__exit__ pre-wired
    session.execute("SELECT 1")
conn.__enter__.assert_called_once()

# 3. Iteration / len / indexing -> MagicMock supplies sane defaults.
rows = MagicMock()
rows.__iter__.return_value = iter([10, 20])  # configure the protocol explicitly
rows.__len__.return_value = 2
assert list(rows) == [10, 20]
assert len(rows) == 2

# 4. A plain Mock cannot do any of the above:
try:
    with Mock():                             # no __enter__ -> TypeError
        pass
except TypeError as exc:
    print("plain Mock has no context protocol:", exc)

Both classes share the auto-creation behavior that masks typos. The guard for that is spec= (or autospec=True), which constrains the attribute surface to a real interface and makes undefined access raise AttributeError — independent of whether you picked Mock or MagicMock:

Python
from unittest.mock import Mock, MagicMock

class PaymentGateway:
    def charge(self, amount: int) -> str: ...

# Without spec, a renamed/typo'd method silently succeeds:
loose = Mock()
loose.chrage(100)                            # auto-created child, test passes (bad)

# With spec, the contract is enforced on either class:
strict = Mock(spec=PaymentGateway)
try:
    strict.chrage(100)                       # not on PaymentGateway -> AttributeError
except AttributeError as exc:
    print("spec caught the typo:", exc)

# spec composes with MagicMock when you ALSO need protocol methods:
mm = MagicMock(spec=PaymentGateway)
mm.charge.return_value = "ok"                # allowed: charge is on the spec
assert mm.charge(100) == "ok"

For asynchronous code under test, neither base class is correct — use AsyncMock (Python 3.8+), which is itself a MagicMock subclass that makes the call awaitable:

Python
import asyncio
from unittest.mock import AsyncMock

repo = AsyncMock()
repo.load.return_value = {"ok": True}

async def main():
    return await repo.load(1)                # awaiting returns the configured value

assert asyncio.run(main()) == {"ok": True}
repo.load.assert_awaited_once_with(1)        # await-aware assertion

If you only need to know which class a piece of code requires, the fastest empirical check is to run it against a plain Mock first: if it raises a TypeError about an unsupported protocol or an AttributeError on a __dunder__, the code needs MagicMock.

Why this works

MagicMock exists precisely because Python looks up dunder methods on the type, not the instance, so a method configured on a plain Mock instance would not be found by with, for, or len() anyway. MagicMock pre-installs those methods at the class level with sensible return values, which is the only reliable way to satisfy the protocols. Auto-creation of regular attributes is shared by both classes and is orthogonal to protocol support, which is why spec= — not the choice of class — is the lever for strictness.

Edge cases and failure modes

  • spec does not restrict dunder methods on MagicMock. A MagicMock(spec=SomeClass) still answers __iter__/__enter__ even if SomeClass is not iterable or a context manager, because the magic methods are configured separately from the spec. Use create_autospec if you need the magic surface to match the real object too.
  • Mock with a magic spec stays non-magic. Mock(spec=list) will not gain __iter__; you would need MagicMock(spec=list). Picking the base class still matters even when a spec is present.
  • Comparisons return mocks, not booleans, on a misconfigured magic method. MagicMock() == MagicMock() is False by default (identity), but __lt__ and friends return child mocks unless configured, so a sort or if mock > 5 can behave unexpectedly. Configure the operator you actually exercise.
  • AsyncMock vs awaiting a MagicMock. Awaiting a plain MagicMock raises TypeError: object MagicMock can't be used in 'await' expression; mixing the two in async tests is a common source of the "coroutine was never awaited" warning covered under debugging async code and event loops.
  • Patch-level state leakage. @patch("mod.Obj", MagicMock) passes the class (one shared instance) instead of new_callable=MagicMock, so state can bleed across tests. Prefer function-scoped pytest fixtures or the default patch autospec to keep instances isolated.

Frequently Asked Questions

What is the actual difference between Mock and MagicMock?MagicMock is a subclass of Mock that pre-configures the common dunder methods (__enter__, __exit__, __iter__, __len__, __getitem__, arithmetic, and more). Plain Mock does not support dunder methods, so it cannot be used directly in a with statement, for loop, or len() call without manual wiring.

Does MagicMock auto-create attributes the way Mock does? Yes. Both classes auto-create child mocks for any undefined non-dunder attribute. The difference is only the pre-wired dunder protocol methods. To stop auto-creation on either class you must pass spec= or spec_set=.

When should I prefer plain Mock over MagicMock? Prefer Mock when the dependency uses no Python protocols and you want a leaner object, or when you want a dunder access like __iter__ to fail loudly instead of silently returning a configured iterator. Add spec= for contract enforcement regardless of which class you pick.

For the strictness side of this decision, the autospec and strict mocking guide shows how create_autospec binds both the attribute surface and signatures to the real object, and the related write-up on resolving side_effect and return_value conflicts covers a behavior both classes share. When the choice is really about how the double reaches the code, see dependency injection for testability and the patterns in patching strategies for complex codebases.

← Back to Deep Dive into unittest.mock