skip to content

return_value and side_effect

Scripting what a mock does when called: a fixed return_value, a sequence of results, a raised exception, or a real callable via side_effect. Interviewers use it to check you can drive error paths.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

In unittest.mock, what does a Mock's return_value attribute control?

level: juniorimportance: must knowfreq 70%

answer

  1. What a mock gives back when called
  2. One object, arguments ignored entirely
  3. Attribute chains configure child mocks
  4. Same object each call, by identity
  5. Unset still returns a stable child

basics

~10 s

return_value is the object a Mock hands back on every call, whatever arguments it receives. Setting client.evaluate.return_value to a dict makes every client.evaluate(...) call return that same dict object, not a fresh copy.

solid answer

~40 s

`return_value` is the single object a `unittest.mock.Mock` returns each time it is called, and it ignores the call arguments entirely — a mock is not a dispatch table. Because attribute access on a bare `Mock` auto-creates a child mock, `client.evaluate.return_value = {"enabled": True}` configures the *child* `client.evaluate`, not `client` itself, and the same chaining works to any depth: `client.session.get.return_value.status = 200`. The value comes back by identity, so if the code under test mutates it, later calls see the mutation. If you never set it, calling the mock is still deterministic: it returns an auto-created child mock, the same object every time, reachable as `mock.return_value`. When one fixed object is not enough — varying results, or an exception — you need `side_effect` instead.

code

python · 11 lines
python
from unittest.mock import Mock

client = Mock()
client.evaluate.return_value = {"enabled": True}

first = client.evaluate("dark-mode")
second = client.evaluate("anything-else")
print(first, first is second)

client.session.get.return_value.status = 200
print(client.session.get("/flags").status)

go deeper

for a junior

Be ready to say that return_value is what the mock returns when called, and to set it on the exact attribute your code calls — client.evaluate.return_value, not client.return_value.

for a middle

Explain that attribute access auto-creates and memoises child mocks, so chained configuration works to any depth, and that the same object comes back by identity on every call regardless of arguments.

for a senior

Show judgment about when a fixed return_value is too blunt: shared mutable results, per-call variation and error paths all need side_effect, and an unconfigured mock returning a truthy child can make a test pass for the wrong reason.

for a principal

Own the guidance on how far a team should script return values at all. Deep return_value chains bake a collaborator's internal shape into every test; a thin hand-written fake or a narrower seam usually costs less to maintain.

A `unittest.mock.Mock` is a callable object that accepts any call, records it, and returns something. `return_value` is the attribute that decides what that something is, and it is the first thing to learn about configuring a test double in Python. ### One object, regardless of arguments Setting `return_value` installs exactly one object as the result of every call. A mock is not a lookup table: it does not match on arguments, so `m(1)` and `m("anything", key=None)` return the identical object. Candidates often expect argument-sensitive behaviour and are surprised when a mock configured "for one input" answers every input the same way. If the result genuinely must depend on the arguments, `return_value` is the wrong tool and a callable `side_effect` is the right one. ### Auto-created children, and where to set it Any attribute you touch on a bare `Mock` is created on demand as a child mock and memoised, so the same attribute access always yields the same child. That is what makes the idiomatic one-liner work: ```python client = Mock() client.evaluate.return_value = {"enabled": True} ``` Here `client.evaluate` materialises a child mock and `return_value` is set on that child — not on `client`. A common beginner error is `client.return_value = {...}`, which configures what `client()` returns and leaves `client.evaluate()` returning a mock. Because children are memoised, chains compose to arbitrary depth: `client.session.get.return_value.status = 200` walks `client.session`, then `.get`, then the mock that `get()` returns, and sets `status` on it. That depth is powerful and dangerous: every dot in such a chain is an assumption your test makes about the collaborator's internal shape. ### The value is returned by identity `return_value` is stored, not copied. The consequence bites in real suites: if the code under test pops a key from the returned dict, appends to the returned list, or closes the returned handle, the *next* call receives the already-mutated object. When each call must get clean state, build a fresh object per call with a callable `side_effect`, or supply a list of separate objects. Even unconfigured, `return_value` exists. The first time the mock is called (or the first time you read `mock.return_value`) a child mock is created and reused thereafter, so `m() is m()` is `True` and `m() is m.return_value` is `True`. This is convenient, and it is also the classic source of a test that passes for the wrong reason: a mock result is truthy, is not `None`, supports attribute access and, if it is a `MagicMock`, supports many dunder operations too. An assertion like `assert result` can therefore pass against a collaborator you forgot to configure. ### Relationship with side_effect `side_effect` is consulted first. If it is set to an exception, an iterable or a callable, `return_value` is bypassed — with one deliberate exception: a callable `side_effect` that returns the `unittest.mock.DEFAULT` sentinel falls back to `return_value`. That pairing lets one callable special-case a few interesting arguments and delegate everything else to the configured default. With `side_effect` set back to `None`, plain `return_value` behaviour resumes. ### Resetting between tests `mock.reset_mock()` deliberately clears recorded usage but leaves configuration in place, precisely because configuration is usually the expensive part of a fixture. To drop the scripted results too, pass the explicit flags: `mock.reset_mock(return_value=True, side_effect=True)`. In practice a fresh mock per test is safer than a shared, mutated one, and re-creating the double per test avoids an entire class of order-dependent failures. ### When it is the right tool `return_value` is the right tool when the collaborator's answer is uninteresting to the test — you need *a* value so the code under test can proceed, and the real assertion is about what your code does with it. It is the wrong tool when the test is about a sequence of outcomes, an error path, or argument-dependent behaviour. Reaching for a three-level `return_value` chain is a signal worth heeding: at that depth the test is describing someone else's internals, and a small hand-written fake, or a narrower seam in the code, usually ages better. These semantics are unchanged across Python 3.10–3.14; `unittest.mock` has been part of the standard library since 3.3. ### The most-missed case: a mocked callable that stands in for a constructor When the code under test builds its collaborator itself, the double you install stands in for the *class*, and calling it returns that mock's `return_value`. So the object your code actually works with is `mock_cls.return_value`, and that is where the method configuration belongs: `mock_cls.return_value.evaluate.return_value = {"enabled": True}`. Candidates who set `mock_cls.evaluate.return_value` instead are configuring a method on the class double that nobody calls, and their test silently exercises an unconfigured instance. The same reasoning explains why every constructed instance in the test is the *same* object: one `return_value`, handed back on each construction.

  • If you never set return_value, what does calling a bare unittest.mock.Mock give you?
    An auto-created child mock — the same object on every call, also reachable as `mock.return_value`. That is why chains like `mock().foo().bar` work with no setup: each call and attribute access materialises a mock and memoises it. It also means the result is truthy and not `None`, so an unconfigured collaborator can make an assertion pass for the wrong reason.
  • The code under test mutates the dict a mock returns. Why does the next call see the mutation?
    `return_value` holds one object and returns it by identity, never a copy. If the code pops a key from that dict, the second call receives the mutated dict. When each call must get fresh state, use a callable `side_effect` that builds a new object per call, or an iterable of distinct objects.
  • How do you clear a configured return_value between tests?
    `mock.reset_mock()` clears recorded usage but deliberately leaves configuration alone; pass `mock.reset_mock(return_value=True, side_effect=True)` to drop the scripted results too. In practice, creating a fresh mock per test is safer than resetting a shared one, because it removes any chance of order-dependent leakage between tests.

A vending machine wired to one slot: whichever buttons you press, the same item drops out — and it is literally the same item, handed back again, not a fresh one.

saying these in an interview costs you the question

  • Thinks return_value varies with the call arguments
  • Sets return_value on the parent instead of the called child
  • Assumes each call gets a fresh copy of the object
  • Believes an unconfigured mock call returns None
  • Uses return_value expecting a different value per call

context

open as a page

What three kinds of value can unittest.mock's side_effect take?

level: middleimportance: must knowfreq 72%

basics

~20 s

side_effect accepts an exception class or instance to raise, an iterable that supplies one result per call, or a callable invoked with the same arguments the mock received. It takes priority over return_value unless the callable returns unittest.mock.DEFAULT.

open as a page

How do you configure a unittest.mock.Mock's nested attributes in one call?

level: middleimportance: should knowfreq 35%

basics

~10 s

Pass dotted keyword arguments: Mock({'evaluate.return_value': True}) at construction, or mock.configure_mock({'close.side_effect': OSError}) afterwards. The constructor forwards its extra keyword arguments to configure_mock, so both forms do the same work.

open as a page

How do you use unittest.mock side_effect to test a retry path that times out twice then succeeds?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Give the mocked call an iterable side_effect whose first entries are exception instances and whose last entry is the success value: two TimeoutError instances, then the payload. Each call consumes one entry, so the retry runs deterministically.

open as a page