What are the three ways to apply unittest.mock.patch, and when is each right?
answer
- Three shapes, one lifecycle
- Decorator, with-block, manual handle
- The decorator hands you an extra argument
- start() must be paired with stop()
- addCleanup makes the restore guaranteed
basics
~20 sunittest.mock.patch can be used as a decorator on a test function or class, as a with-statement context manager, or started and stopped by hand through a patcher object. Every form puts the original attribute back when its scope ends.
solid answer
~40 s`unittest.mock.patch` replaces an attribute for a limited scope and restores it afterwards; the three forms differ only in what defines that scope. As a decorator, `@patch("mod.helper")` covers one test function and passes the replacement in as an extra positional argument (after `self` on a `TestCase` method); applied to a class it covers every test method. As a context manager, `with patch("mod.helper") as fake:` scopes the replacement to a block, which is right when only part of the test needs it. `patcher = patch("mod.helper")` plus `patcher.start()` hands the lifetime to you and must be paired with `patcher.stop()` — inside `unittest`, register `self.addCleanup(patcher.stop)` immediately so an exception cannot leak it. The related entry points share the lifecycle: `patch.object` when you already hold the object, `patch.dict` for mapping entries, `patch.multiple` for several attributes of one target.
code
python · 24 linesimport time
import unittest
from unittest.mock import patch
class ClockTests(unittest.TestCase):
def setUp(self):
patcher = patch("time.monotonic", return_value=5.0)
self.monotonic = patcher.start()
self.addCleanup(patcher.stop)
@patch("time.time", return_value=1.0)
def test_decorator_form(self, mock_time):
self.assertEqual(time.time(), 1.0)
self.assertEqual(time.monotonic(), 5.0)
def test_context_manager_form(self):
with patch("time.time", return_value=2.0) as fake:
self.assertEqual(time.time(), 2.0)
fake.assert_called_once_with()
self.assertNotEqual(time.time(), 2.0)
unittest.main(argv=["ignored"], exit=False)go deeper
Be ready to write all three forms from memory and to say what the decorator's extra parameter is: the replacement object, arriving after self. Knowing that every form restores automatically except start() is the point of the question.
Explain the mechanics: patch records the previous value, installs a MagicMock unless told otherwise, and restores in a finally. Be able to justify choosing a with-block for a narrow scope and to name patch.object and patch.dict as the same lifecycle applied to an object attribute and a mapping entry.
Show the production habit: start() in setUp is paired with addCleanup in the same breath, because tearDown does not run when setUp raises. An interviewer expects you to have debugged a suite that leaked a patch and to describe how you found it.
Own the convention. Decide what your codebase's default form is, whether class-level patching is allowed at all, and how test isolation is enforced — randomized ordering, autospecced doubles, a lint rule against bare start(). The tradeoff is setup cost against a whole class of order-dependent failures.
`unittest.mock.patch` does one thing: it temporarily replaces an attribute on some object — usually a module attribute, but it can be any attribute of any object — and puts the original back when the scope it was given ends. Everything else about it is packaging around that single idea, and the three application forms exist because tests need three different scope shapes. ## As a decorator `@patch("mod.helper")` wraps the test function. When the function is called, `patch`: 1. starts, 2. builds a replacement (a `MagicMock` by default), 3. binds it to `mod.helper`, 4. calls your function with the replacement appended to the positional arguments, 5. and stops the patch in a `finally` — so it is undone even if the test raises or the assertion fails. The extra parameter goes after `self` on a `TestCase` method. Applied to a class instead of a function, `patch` decorates every method whose name starts with the test prefix, which is a convenient way to give a whole test class the same replacement without repeating the decorator. ## As a context manager `with patch("mod.helper") as fake:` scopes the replacement to a block. This is the form to reach for when only part of the test should see the double — set up real state first, patch for the one call you want to intercept, then assert on the real objects afterwards. It also composes with `contextlib.ExitStack` when the number of patches is computed rather than written literally, and it reads better than a five-deep decorator stack. ## Manually, with start and stop `patcher = patch("mod.helper")` builds the patcher without activating it. `fake = patcher.start()` activates it and returns the replacement; `patcher.stop()` deactivates it. Nothing else will call `stop()` for you — this is the only form where the restore is your responsibility, and it is **the form that leaks**. The safe idiom inside `unittest` is to register the teardown immediately: `self.addCleanup(patcher.stop)` right after `start()`, because cleanups registered before a failure still run even when `setUp` raises afterwards and `tearDown` is skipped. `patch.stopall()` stops every patcher started this way and not yet stopped. ## The related entry points share the lifecycle - `patch.object(SomeClass, "method")` patches an attribute of an object you already hold, which avoids writing a dotted string and lets a linter and a refactor tool see the reference. - `patch.dict(mapping, {"k": "v"})` patches *entries* in a dictionary rather than an attribute — the canonical use is `os.environ` — and on exit restores the mapping's original contents, including keys the block deleted; `clear=True` empties it for the duration first. - `patch.multiple(target, a=DEFAULT, b=DEFAULT)` patches several attributes of one target at once, passing `unittest.mock.DEFAULT` for each attribute you want a fresh mock for; in the decorator form the replacements arrive as keyword arguments named after the attributes, and in the context-manager form as a dict. ## What "restore" actually means `patch` records the attribute's previous value at start and reassigns it at stop. - If the attribute did not exist and you passed `create=True`, the restore deletes it again rather than setting `None`. - If the attribute was inherited rather than defined on the object you patched, `patch` deletes the attribute it added so that lookup falls back to the base class again instead of pinning a copy on the subclass. - Patches nest and unwind in reverse order, so an inner patch of the same target is undone before the outer one. ## Choosing between the forms - Default to the **decorator** when the whole test needs the replacement and you have one or two of them: it is the least code and the restore is automatic. - Use the **context manager** when the scope is narrower than the test, when you need the patch inside a loop or a branch, or when a long decorator stack has become hard to read. - Use `start()`/`stop()` only when the lifetime genuinely spans several methods — a patch shared by every test in a class, established in `setUp` — and always pair it with `addCleanup`, or with `addClassCleanup` when it is established in `setUpClass`. Two practical notes that come up immediately after this question. 1. First, `patch` raises `AttributeError` at start if the target attribute does not exist, which catches typos in the string target — but it cannot catch a *correct* name patched in the wrong module, which is the separate and much more common failure. 2. Second, the object `patch` installs is a `MagicMock` unless you say otherwise: `new=` supplies an exact object to install (and then nothing is passed to your test), while `new_callable=` names a factory to build the replacement with, which is how you install a `PropertyMock` or a plain `Mock` instead of the default.
- When would you reach for patch.object instead of the dotted-string form of patch?When you already hold the object in the test — a class you imported, an instance, a module. `patch.object(Exporter, "batch_size")` names the attribute against a real reference, so a typo or a rename is caught by the import and by refactoring tools, instead of hiding in a string that is only resolved when the test runs. It is the same patcher underneath, with the same decorator, context-manager and start/stop forms.
- How do you replace a single environment variable for the duration of one test?`with patch.dict(os.environ, {"EXPORT_BATCH": "340"}):` — `patch.dict` patches entries in a mapping rather than an attribute, and on exit restores the mapping's original contents, including any keys the block added or deleted. Passing `clear=True` empties the mapping first, which is how you test the code path where the variable is absent. It works on any dict, not just `os.environ`.
- What does applying @patch to a unittest.TestCase subclass do?It decorates every method whose name starts with the test prefix, as if the decorator had been written on each one, and its mock is appended after any contributed by a method's own decorators. It is a way to give a whole class the same replacement without repeating yourself — at the cost that adding one shifts the parameter list of every method in the class.
The three forms are three lease lengths on the same borrowed attribute: the decorator's lease expires with the test, the with-block's with the block, and start() is an open-ended lease you have to hand back yourself.
saying these in an interview costs you the question
- Thinks patch replaces the attribute permanently for the process
- Calls patcher.start() and never calls stop()
- Believes a with patch(...) block needs manual teardown
- Puts start() in setUp with no addCleanup or tearDown
- Assumes patch.object also needs a dotted string target
- Cannot say what the decorator's extra argument is