What do len() and iteration return on a MagicMock by default, and why is that dangerous in a test?
answer
- The double never argues with you
- Empty and zero are valid answers
- The loop body simply never ran
- Configure the dunder, then assert positively
- __len__ gives 0, __iter__ gives nothing
basics
~20 sA MagicMock is length zero and iterates as empty, while still being truthy. So a loop over one never executes and a test asserting on its results can pass while proving nothing — the classic vacuous green.
solid answer
~40 s`MagicMock` pre-configures the dunder methods with deliberately bland defaults: `__len__` returns `0`, `__iter__` returns an empty iterator, `__bool__` returns `True`, `__contains__` returns `False`, `__int__` returns `1`, `__exit__` returns `False`, and `__enter__` returns a child mock. The dangerous pair is the first two. A `for` loop over an unconfigured `MagicMock` body never runs, so no exception is raised, nothing is called, and any assertion phrased as "no errors were recorded" or "the sink was not called with bad data" passes vacuously. The defence is to configure the dunder explicitly — `m.__len__.return_value = 2`, `m.__iter__.side_effect = lambda: iter(items)` — and to assert on a *positive* fact, such as an exact processed count or exact call arguments, so an empty double fails the test rather than satisfying it.
code
pycon · 10 lines>>> from unittest.mock import MagicMock
>>> reader = MagicMock()
>>> len(reader)
0
>>> list(reader)
[]
>>> bool(reader)
True
>>> reader.__exit__(None, None, None)
Falsego deeper
Memorise the two defaults that bite: an unconfigured MagicMock has length zero and iterates as empty. If a loop over your double never seems to run, that is why.
Explain how MagicMock configures dunders on the type and delegates to child mocks, and set them correctly — including using side_effect so a second pass over iter is not empty.
Diagnose the vacuous pass. Show how a green suite can sit on top of a loop that never executed, and rewrite the assertions positively so an empty double fails instead of agreeing.
Own the standard that keeps this out of the codebase: assertions on positive facts, a required demonstration that a new test can fail, and a team position on how much behaviour a double may silently invent.
`MagicMock` differs from `Mock` in one respect: it comes with the magic methods already configured. `Mock` leaves them alone, so `len(Mock())` raises `TypeError: object of type 'Mock' has no len()` and a `with` block over one raises a `TypeError` naming the missing `__exit__`. `MagicMock` supports them — and the values it supports them *with* are the interesting part. ## The defaults, and why they were chosen * `__len__` -> `0` * `__iter__` -> `iter([])` * `__bool__` -> `True` * `__contains__` -> `False` * `__int__` -> `1`, `__float__` -> `1.0`, `__index__` -> `1` * `__exit__` -> `False` (so exceptions inside a `with` block propagate) * `__enter__` -> a child mock, `m.__enter__.return_value` * `__eq__` / `__ne__` -> identity comparison, so a mock equals only itself * ordering dunders are *not* usefully configured: `m < other` still raises `TypeError` The values had to be something, and returning a mock from `__len__` is impossible — the interpreter requires a real non-negative `int`. Zero and the empty iterator are the honest choices for "nothing configured", but they are also perfectly valid answers, which is precisely the hazard: the double does not complain, it agrees. ## The vacuous pass Consider a log-ingest pipeline whose `process()` walks a reader and hands each parsed record to a sink. A test doubles the reader with a bare `MagicMock` and asserts that the error sink recorded nothing. The loop body never runs — `__iter__` yielded nothing — so of course no errors were recorded, and the suite is green on a function that did nothing at all. This gets genuinely expensive when a refactor moves the seam. Suppose the ingest module is restructured to break a circular import at startup, and the collaborator the test doubles is now a slightly different object: the double keeps satisfying every call, iteration keeps yielding nothing, and the suite keeps passing while the 6-hour nightly run silently writes zero records. Nothing in the test framework can distinguish "iterated nothing" from "iterated correctly over an empty input" — only the assertions can. ## Configuring the dunders Magic methods are looked up on the *type*, not the instance, so you cannot fake them by assigning a plain function to an instance attribute. `MagicMock` handles that for you: the attributes it exposes as `m.__len__`, `m.__iter__` and so on are child mocks whose configuration the type-level hooks consult. ```python m.__len__.return_value = 2 m.__iter__.return_value = iter(["line-1\n", "line-2\n"]) ``` The second line has a trap of its own: an iterator is consumed once, so a second pass over the double yields nothing and a function that iterates twice half-works. Use a `side_effect` that builds a fresh iterator per call instead: ```python m.__iter__.side_effect = lambda: iter(["line-1\n", "line-2\n"]) ``` `__enter__` is worth configuring whenever the code does `with collaborator as thing:` — by default `thing` is an anonymous child mock rather than the collaborator itself, which is a common source of "my assertions are on the wrong object". Setting `m.__enter__.return_value = m` makes the double behave like the many real objects that return `self` from `__enter__`. ## Writing assertions that cannot pass vacuously The structural fix is to assert positively. Instead of "the error sink was not called", assert the exact number of records handed to the success sink and the exact arguments of at least one of them. Instead of "no exception was raised", assert a return value. A test that asserts only absences will pass against a double that does nothing, against a function body that was deleted, and against a loop that never started. Two habits help. First, make the double's inputs explicit — if the test cares that three records were read, configure three and assert three came out. Second, prove the test can fail: break the code deliberately once and confirm the assertion goes red. A test that has never failed for the reason it exists is a test whose green means nothing, and `MagicMock`'s agreeable defaults are one of the easiest ways to acquire a large collection of them.
- Why can't you fake a magic method by assigning a function to an instance attribute of a mock?Because the interpreter looks up implicit dunder invocations on the type, never on the instance: `len(obj)` consults `type(obj).__len__`. Assigning `obj.__len__ = lambda: 3` stores an instance attribute that `len()` ignores. `MagicMock` solves it by defining the dunders on its type and delegating each to a per-instance child mock, which is why `m.__len__.return_value = 3` is the supported spelling.
- You set m.__iter__.return_value = iter([1, 2]) and the code iterates twice. What happens?The first pass sees `[1, 2]` and the second sees nothing, because a single iterator object is exhausted after one pass and `return_value` hands back that same object every time. Use `m.__iter__.side_effect = lambda: iter([1, 2])` so each call builds a fresh iterator, mirroring how a real list behaves under repeated iteration.
- A MagicMock has __len__ of 0 but is still truthy. Why doesn't the zero length make it falsy?Truth testing calls `__bool__` first and only falls back to `__len__` when `__bool__` is absent. `MagicMock` configures `__bool__` to return `True`, so it short-circuits the length. That is usually what you want — a collaborator that vanishes in an `if` guard would be worse — but it means `if not results:` guards never fire against an unconfigured double.
It is a witness who answers every question with a polite "nothing to report". Ask whether anything went wrong and you get a clean bill of health — because they were never in the room.
saying these in an interview costs you the question
- Assumes a MagicMock iterates as a non-empty sequence
- Thinks zero length makes the mock falsy
- Sets a dunder as an instance attribute and expects it to work
- Reuses one iterator for __iter__ across two passes
- Trusts a test that only asserts something did not happen
- Expects __enter__ to return the mock itself by default