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+ (
MagicMockandMockare unchanged in this respect since 3.3;AsyncMockrequires 3.8). unittest.mockfrom the standard library.- Optional:
pytest7.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.
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:
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:
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
specdoes not restrict dunder methods onMagicMock. AMagicMock(spec=SomeClass)still answers__iter__/__enter__even ifSomeClassis not iterable or a context manager, because the magic methods are configured separately from the spec. Usecreate_autospecif you need the magic surface to match the real object too.Mockwith a magic spec stays non-magic.Mock(spec=list)will not gain__iter__; you would needMagicMock(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()isFalseby default (identity), but__lt__and friends return child mocks unless configured, so a sort orif mock > 5can behave unexpectedly. Configure the operator you actually exercise. AsyncMockvs awaiting aMagicMock. Awaiting a plainMagicMockraisesTypeError: 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 ofnew_callable=MagicMock, so state can bleed across tests. Prefer function-scopedpytestfixtures or the defaultpatchautospec 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