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 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:
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.
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.
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
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.
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.
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
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.
Related
- Mock vs MagicMock vs AsyncMock — when to use each extends this two-way decision to the full three-class picture, including the async case.
- Autospec and strict mocking shows how
create_autospecbinds both the attribute surface and call signatures to the real object — the strictness lever this guide keeps pointing at. - Resolving side_effect and return_value conflicts covers a configuration behavior both
MockandMagicMockshare. - Dependency injection for testability is where to look when the real question is how the double reaches the code under test.
- Patching strategies for complex codebases handles the
@patchtargeting and lifetime concerns that sit around either class.
← Back to Deep Dive into unittest.mock