A mock configured with mock = Mock(return_value="ok"); mock.side_effect = [1, 2] returns 1, then 2, then raises StopIteration instead of falling back to "ok" — and a callable side_effect that quietly returns None makes the mock return None no matter what return_value you set. Both surprises trace to the same rule: unittest.mock.Mock.__call__ evaluates side_effect before it ever looks at return_value. This guide pins down that precedence chain and gives you the deterministic patterns to stop side_effect from silently shadowing return_value.
Prerequisites
- Python 3.8+ (the precedence rules below are unchanged since 3.6;
AsyncMockrequires 3.8). unittest.mockfrom the standard library — no third-party packages required.- Optional:
pytest7.0+ if you reproduce the conflicts inside a test runner.
Solution
The fix is to understand the precedence chain, then commit to exactly one configuration axis per call. Mock.__call__ resolves a return in this fixed order:
- If
side_effectis an exception class or instance, raise it. - If
side_effectis a callable, call it with the same args; if it returns the sentinelunittest.mock.DEFAULT, fall through toreturn_value, otherwise return its result. - If
side_effectis an iterable, callnext()on it and return the yielded value (raisingStopIterationwhen exhausted). - Otherwise (
side_effect is None), returnreturn_value.
The code below demonstrates each branch and the one supported way to use both attributes together — the DEFAULT sentinel.
import unittest.mock as mock
# --- Branch 4: side_effect is None, so return_value wins ---
m = mock.Mock(return_value="static")
assert m() == "static" # no side_effect set -> falls through
# --- Branch 2/3: side_effect set, return_value is bypassed ---
m = mock.Mock(return_value="static")
m.side_effect = [1, 2] # an iterable side_effect
assert m() == 1 # next() on the iterable
assert m() == 2
try:
m() # iterable exhausted...
except StopIteration:
pass # ...raises, does NOT fall back to "static"
# --- The supported bridge: return mock.DEFAULT to use return_value ---
m = mock.Mock(return_value="fallback")
def route(value):
# Return a real value for "live", otherwise defer to return_value.
return "dynamic" if value == "live" else mock.DEFAULT
m.side_effect = route
assert m("live") == "dynamic" # callable produced a value
assert m("other") == "fallback" # callable returned DEFAULT -> return_value
# --- Exception branch takes priority over everything ---
m = mock.Mock(return_value="never")
m.side_effect = TimeoutError("upstream down")
try:
m()
except TimeoutError:
pass # raised before return_value is consulted
When you genuinely need a sequence of replies and a stable tail value, encode the whole policy in one callable rather than leaning on implicit fallback:
import unittest.mock as mock
def sequenced(values, default):
"""A callable side_effect that yields `values` in order, then `default` forever."""
it = iter(values)
def _call(*args, **kwargs):
try:
return next(it) # consume the scripted sequence
except StopIteration:
return default # explicit, deterministic tail — never raises
return _call
api = mock.Mock()
api.side_effect = sequenced([200, 200, 503], default=200)
assert [api() for _ in range(5)] == [200, 200, 503, 200, 200]
To reset a mock between reuses without leaving a stale iterator cursor behind, use the side_effect flag on reset_mock (added in Python 3.6):
m = mock.Mock(side_effect=[1, 2])
m() # consumes 1
m.reset_mock(side_effect=True) # clears call history AND side_effect
m.side_effect = [9, 8] # fresh iterable, cursor at start
assert m() == 9
The same precedence rules govern AsyncMock, but the value must be awaitable-compatible. A list side_effect is consumed by iteration (not by awaiting each item), and a callable side_effect may be sync or async. Wrapping a sequence in an async callable keeps behavior deterministic when the iterable runs dry:
import asyncio
import unittest.mock as mock
def async_sequenced(values, default):
it = iter(values)
async def _call(*args, **kwargs):
try:
return next(it)
except StopIteration:
return default
return _call
client = mock.AsyncMock()
client.side_effect = async_sequenced(["a", "b"], default="z")
async def main():
return [await client(), await client(), await client()]
assert asyncio.run(main()) == ["a", "b", "z"]
Why this works
Mock.__call__ never merges the two attributes; it short-circuits on the first non-None side_effect it finds, and only an explicit None (or a callable returning mock.DEFAULT) lets control reach return_value. By collapsing all dynamic behavior into a single callable, you remove the implicit iterator state that causes StopIteration and the silent None returns that come from a callable forgetting to return a value. Choosing one axis per call makes the mock's output a pure function of its configuration rather than of hidden cursor position.
Edge cases and failure modes
- Iterable exhaustion across parametrized tests. A fixture that returns
Mock(side_effect=[1, 2, 3])keeps one iterator across@pytest.mark.parametrizeruns; the third reuse raisesStopIteration. Build the mock fresh per test (function-scoped fixture) or reset withreset_mock(side_effect=True). - Callable that forgets to return. A
side_effectlambda used for its side effect only (e.g. logging) returnsNone, so the mock returnsNoneandreturn_valueis silently ignored. Returnmock.DEFAULTexplicitly when you want the fallback. StopIterationinside a generator under test. If the code under test runs inside a generator, aStopIterationleaking from an exhaustedside_effectis converted toRuntimeErrorby PEP 479. The traceback then points at the generator, not the mock — checkmock.call_countagainst the iterable length.AsyncMockwith a non-awaitable result. A sync callableside_effectthat returns a plain value works (AsyncMock wraps it), but returning a coroutine object that the caller never awaits produces a "coroutine was never awaited" warning. The debugging track covers this in tracing unawaited coroutine warnings.reset_mock()without flags leaves config intact. Plainreset_mock()clearscall_countandmock_callsbut deliberately keepsside_effectandreturn_value. Passside_effect=True/return_value=Trueto clear them too.
Frequently Asked Questions
Does setting side_effect to None restore return_value behavior?
Yes. Assigning side_effect = None clears the override so the mock falls back to return_value. But if the mock previously held an exhausted iterable, that cursor is gone once you reassign, so the next call uses return_value cleanly. To also clear call history, pair it with reset_mock().
Why does my mock return None even though I set return_value?
Because side_effect is set and takes precedence. If side_effect is a callable, its return value is used; if it returns None, the mock returns None and return_value is ignored. Wrap the fallback inside the callable, or set side_effect = None.
Can side_effect and return_value be used together?
Not as a blend. Mock.__call__ checks side_effect first; if it is a callable, iterable, or exception, return_value is bypassed for that call. The one bridge is returning the sentinel unittest.mock.DEFAULT from a callable side_effect, which tells Mock to fall through to return_value.
These conflicts are easiest to avoid when the mock's interface is constrained in the first place — pairing this with autospec and strict mocking stops a typo'd attribute from silently absorbing your return_value, and the broader mechanics live in the deep dive into unittest.mock. When the conflicting mock is injected rather than patched, the constructor-level approach in dependency injection for testability makes the configuration explicit at the call site.
← Back to Autospec & Strict Mocking