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)
Choosing between Mock, MagicMock and AsyncMock If the target is an async coroutine use AsyncMock; otherwise, if the code under test drives the double through a Python protocol (with, for, len, indexing or arithmetic) use MagicMock, else use plain Mock. On any branch, adding spec or autospec makes undefined attributes raise AttributeError. Which mock class does this double need? code under test needs a double Is the target an async def / coroutine? yes AsyncMock awaitable · assert_awaited_* no Does the code drive the double through a Python protocol? with · for · len() · [ ] · + yes no MagicMock dunder protocols pre-wired __enter__ · __iter__ · __len__ Mock plain calls & attribute reads leanest · fails loud on dunders On any branch: add spec= / autospec=True so undefined attributes raise AttributeError
The class choice turns on protocol use, not on strictness — spec is an orthogonal lever you can add to whichever class the branch selects.

Both classes share the auto-creation behavior that masks typos. The guard for that is spec= (or the fuller autospec strict mocking via 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.

The practical consequence is that the two classes fail in opposite directions: Mock fails loudly the moment production code uses a protocol you did not anticipate, while MagicMock absorbs that same usage and reports success. The diagram below traces one len(obj) call through both.

One len() call through Mock and through MagicMock A sequence diagram with three lanes: the code under test, a Mock double, and a MagicMock double. Calling len on the Mock raises TypeError because no dunder is configured, while the same call on a MagicMock resolves through the preconfigured dunder and returns zero, so the test passes without exercising anything real. One len() call through Mock and through MagicMock code under test Mock() MagicMock() len(obj) TypeError: no __len__ len(obj) returns 0 silently A returned 0 is a real value, so the assertion downstream passes for the wrong reason.
The same call is a hard failure against Mock and a silent zero against MagicMock — which is exactly the trade-off you are choosing between.

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.

Choosing under review pressure

The class you reach for changes what a future reviewer can conclude from a green test, so it is worth having a rule that survives code review rather than a preference.

Default to Mock for collaborators you own. Objects defined inside your codebase have a small, knowable surface; if the code under test starts iterating over a repository handle that used to be a single-object fetcher, you want that change to break the test at the moment it is introduced. Mock gives you that break for free, because the missing __iter__ raises TypeError rather than yielding an empty sequence.

Reach for MagicMock when the collaborator is a container-like or context-manager-like third-party object whose protocol surface you would otherwise have to reimplement by hand. A database cursor used as with conn.cursor() as cur: needs __enter__ and __exit__; configuring those on a plain Mock costs three extra lines per test and gains nothing, because the protocol is stable and owned by someone else.

Reach for create_autospec whenever the answer to "which class is this standing in for?" is a real class you can import. Autospec subsumes the choice entirely: it builds a MagicMock under the hood but constrains it to the spec, so you keep protocol support without the blanket permissiveness.

Python
from unittest.mock import Mock, MagicMock, create_autospec
from myapp.repo import OrderRepository

repo = Mock()                              # owned collaborator: fail on drift
cursor = MagicMock()                       # third-party protocol object
with cursor as c:                          # __enter__/__exit__ already work
    c.execute("SELECT 1")

typed = create_autospec(OrderRepository)   # best of both: protocols + contract
typed.fetch(order_id=7)                    # signature enforced at call time
What each default costs you on a real collaborator A comparison table across Mock, MagicMock and create_autospec covering four properties: whether an unexpected dunder use fails, whether an unknown attribute fails, whether method signatures are enforced, and how many lines of setup a context-manager collaborator needs. What each default costs you on a real collaborator Criterion Mock() MagicMock() create_autospec Unexpected dunder use fails yes no yes Unknown attribute fails no no yes Signature enforced no no yes Setup for a with-block 3 lines none none
The permissiveness that makes MagicMock convenient is the same property that lets an interface change slip past a green test.

The one anti-pattern to name explicitly in review: a MagicMock used as a value rather than as a collaborator. Passing one into a function that does arithmetic on it produces another MagicMock from __add__, and the assertion at the end compares two mocks and passes. If the double is standing in for data, use the data.

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.

← Back to Deep Dive into unittest.mock