skip to content

In what order do stacked unittest.mock.patch decorators pass their mocks to a test?

level: middleimportance: should knowfreq 50%

answer

  1. Read the stack from the bottom
  2. Decorators apply inside-out
  3. Closest to def, first in the signature
  4. new= adds no parameter at all
  5. Class-level patch arrives last

basics

~10 s

Bottom-up: the decorator written closest to the def is applied first and supplies the first mock parameter, and the topmost decorator supplies the last. On a TestCase method the mocks follow self.

solid answer

~40 s

Stacked `patch` decorators are ordinary decorators, so they apply bottom-up, and each appends its replacement to the positional arguments as its wrapper runs. The decorator nearest the `def` therefore matches the leftmost mock parameter (after `self`), and the topmost matches the rightmost. Because the pass is positional, the parameter names are yours and nothing checks them — swapping two names gives a test that configures one double and asserts on another without raising. Two adjustments matter: `patch(target, new=obj)` installs `obj` and passes **no** argument, shifting everything below it, while `new_callable=` still lets `patch` build the object so the argument stays. `patch.multiple` passes keyword arguments named after the attributes instead. A class-level `patch` acts as the outermost decorator, so its mock arrives last.

code

python · 14 lines
python
import os
import time
from unittest.mock import patch


@patch("os.getpid")
@patch("time.time")
def check(mock_time, mock_getpid):
    assert mock_time is time.time
    assert mock_getpid is os.getpid
    print("bottom decorator supplies the first argument")


check()

go deeper

for a junior

Remember the direction: bottom decorator, first parameter. Given a two-deep stack you should be able to write the signature correctly and say that self still comes first on a TestCase method.

for a middle

Explain why — decorators apply inside-out and each wrapper appends its replacement positionally — and know the two exceptions that change the count: new= contributes nothing, patch.multiple contributes keywords. Naming parameters after their targets is the defence you should volunteer.

for a senior

Treat a deep stack as a smell. Show that a swapped pair fails silently rather than loudly, and describe your preferred alternative — context managers, ExitStack, or injecting the collaborator so no patch is needed — plus what a class-level patch does to every existing signature.

for a principal

Decide the house style: how much patching a unit test may do before the design is the problem, and whether collaborators are injected rather than replaced. The ordering rule is trivia; the number of doubles a test needs is an architecture signal you own.

Stacked `patch` decorators are ordinary decorators, and Python applies decorators bottom-up: the one written closest to the `def` wraps the function first, and the ones above it wrap that wrapper. `unittest.mock` appends each replacement to the positional arguments as its own wrapper runs, so the innermost — bottom — patch contributes the *first* extra argument, and the topmost contributes the last. ```python @patch("os.getpid") # applied last -> second mock argument @patch("time.time") # applied first -> first mock argument def test_export(self, mock_time, mock_getpid): ... ``` Read the decorator list from the bottom up and the parameter list from the left, and the two line up. On a `TestCase` method `self` stays first; the mocks follow it. **The parameter names are yours.** `patch` passes positionally, so nothing validates that `mock_time` corresponds to `time.time` — a swapped pair of names produces a test that configures the wrong double and then asserts against the wrong one. That is the whole hazard of the ordering rule: getting it backwards does not raise, it produces confidently wrong assertions. Naming each parameter after its target, in the same order, is the cheap defence. **`new=` removes a parameter.** `patch(target, new=obj)` installs exactly `obj` and, because there is nothing to hand you that you did not already have, passes *no* extra argument. Mixing an explicit `new=` into a stack therefore shifts every parameter below it: ```python @patch("time.monotonic", new=lambda: 100.0) # contributes no argument @patch("time.time") # contributes the only argument def test_export(mock_time): ... ``` The same is true of the context-manager form: `with patch(t, new=obj) as x:` binds `x` to `obj` itself rather than to a mock, which is why `with patch(t, new=obj):` with no `as` is the usual spelling. **`new_callable=` keeps the parameter.** It names a factory that `patch` calls to build the replacement — `new_callable=Mock` for a plain mock without dunder support, `new_callable=PropertyMock` to replace an attribute that is accessed rather than called. Since `patch` still constructs the object, it still hands it to you, so the argument stays and the ordering is unaffected. **`patch.multiple` passes keywords.** Decorating with `patch.multiple("time", time=DEFAULT, monotonic=DEFAULT)` injects `time=` and `monotonic=` as keyword arguments named after the attributes, not positionally, so those parameters are matched by name and their position among the other mock parameters does not matter. In the context-manager form the same replacements arrive as a dict keyed by attribute name. **Class decoration interacts with method decoration.** When `patch` decorates a `TestCase` class, it behaves as if it were the outermost decorator on every test method — its mock is appended *after* the ones contributed by the method's own decorators. So a class-level patch plus two method-level patches gives `def test_x(self, m_bottom, m_top, m_class)`. This is the arrangement people most often get wrong when they add a class-level patch to an existing suite and every method's signature silently shifts. **When the ordering stops being worth it.** Three stacked decorators are readable; six are a signature nobody can verify by eye, and each addition renumbers the ones below. At that point the context-manager form is better: each `with patch(...) as fake_x:` binds its own name at the point of use, so there is no positional correspondence to maintain, and `contextlib.ExitStack` handles a variable number of them. A long stack is also frequently a design signal — a unit that needs six collaborators replaced is a unit with six collaborators. **A note on what the argument actually is.** Each injected parameter is the replacement object that is currently installed at the target, which by default is a `MagicMock`. It is not the patcher, and not the original — if you need the original inside the test, capture it before the patch starts or reach for it through the mock's configuration. The rule to keep is short: decorators apply bottom-up, arguments arrive left-to-right in that same order, `new=` contributes nothing, `new_callable=` contributes one, `patch.multiple` contributes keywords, and a class-level patch is last. **Mixing decorator kinds changes nothing.** `patch.object(SomeClass, "method")` stacked with a string-target `patch` follows exactly the same bottom-up rule; the two forms are the same patcher with a different way of naming the target, and each contributes one positional argument unless it was given `new=`. The same applies to `patch.dict`, which contributes no argument at all — there is nothing to hand back, since the mapping itself is patched in place. **How to settle an argument about a stack without reasoning.** Inside the test, assert the identity you believe holds: `assert mock_time is time.time`. If the parameter really is the double installed at that target, the assertion passes; if the names are swapped, it fails immediately and loudly instead of producing a wrong assertion later. It is a two-line check worth writing once when you inherit a deep stack you did not author, and it is also the fastest way to convince yourself of the ordering rule in a REPL.

  • What changes in the signature if one stacked decorator passes new= explicitly?
    That decorator contributes no parameter, because `patch` is installing an object you already have rather than building one for you. Every parameter that would have come from decorators above it shifts one position left, which is a common source of silently mismatched names in an existing stack. `new_callable=` is the opposite: `patch` still constructs the replacement, so the argument is still passed.
  • How does patch.multiple hand its replacements to a decorated test?
    As keyword arguments named after the patched attributes — `patch.multiple("time", time=DEFAULT, monotonic=DEFAULT)` gives the test `time=` and `monotonic=` parameters, matched by name rather than position, so they are immune to the ordering rule. In the context-manager form the same replacements come back as a dict keyed by attribute name.
  • When would you abandon the decorator stack entirely?
    Once the stack is deep enough that no reader can verify the signature by eye — around three or four. Context managers bind each double to a name at the point of use, so there is no positional correspondence to maintain, and `contextlib.ExitStack` covers a computed number of them. A stack that keeps growing is also a design signal: a unit needing six collaborators replaced has six collaborators.

saying these in an interview costs you the question

  • Reads a decorator stack top-down for argument order
  • Assumes new= still injects a parameter
  • Thinks the parameter names must match the targets
  • Puts the mock parameters before self
  • Believes a swapped pair of mocks would raise
  • Forgets a class-level patch shifts every method signature

context