Batu Lab NotesPractical developer guides

Why does Mock side_effect stop with StopIteration?

By Batu ยท English technical notes

Why does Mock side_effect stop with StopIteration?

A Mock with list-backed side_effect stops with StopIteration because each call consumes one item from the configured iterable. Once the fixture makes a third call after two scripted responses, there is no next item to provide.

The first mock has exactly two values: first and second. The assertions establish that those are returned in order. The third invocation raises StopIteration; it does not prove that a real dependency failed, timed out, or returned an invalid response. It proves only that this test double's response script was exhausted. Checking call_count == 3 also shows that the failed invocation still counts as an attempted call.

If the behavior should continue after a finite sequence, encode that rule instead of relying on an oversized list. The second mock uses a callable fallback that returns a deterministic value for every call. Its two calls yield separate values and record their arguments. A callable can also raise domain-specific exceptions or return DEFAULT when a configured return_value should apply.

unittest.mock has been available in the standard library since Python 3.3. The official side_effect documentation specifies that iterables provide one value per call.

AI assistance disclosure: this article and executable synthetic fixture were drafted with AI assistance.

from unittest.mock import Mock

scripted = Mock(side_effect=["first", "second"])
assert scripted() == "first"
assert scripted() == "second"

try:
    scripted()
except StopIteration:
    exhausted = True
else:
    exhausted = False
assert exhausted
assert scripted.call_count == 3


def fallback(index):
    return f"fallback-{index}"

continuous = Mock(side_effect=fallback)
assert continuous(3) == "fallback-3"
assert continuous(4) == "fallback-4"
continuous.assert_any_call(3)
continuous.assert_any_call(4)

print("scripted responses: first, second")
print("third scripted call: StopIteration")
print("callable fallback: fallback-3, fallback-4")
scripted responses: first, second
third scripted call: StopIteration
callable fallback: fallback-3, fallback-4