Most code calls its dependencies directly, so patching is how you mock them without changing the code.
patch("module.name") replaces an object for the duration of a test and restores it afterwards.
with patch("myapp.billing.requests.post") as post:
post.return_value.status_code = 200
charge(10)As a decorator, @patch("target") passes the mock as an extra argument; stacked decorators pass mocks bottom-up. patch.object(obj, "attr") patches an attribute of an object you already have, and patch.dict(os.environ, {"MODE": "test"}) patches a dictionary.
The rule that trips everyone: patch where the name is looked up, not where it's defined. If myapp/billing.py does from datetime import datetime and calls datetime.now(), patch myapp.billing.datetime, because billing holds its own reference. Patching datetime.datetime has no effect there (and fails anyway: built-in types can't be patched). If the module does import requests and calls requests.post, patching requests.post works because the lookup happens through the module at call time.
Useful options: return_value=, side_effect=, new= to provide a specific replacement, and autospec=True (next level). In setUp, use self.enterContext(patch(...)) so the patch is undone automatically.
Example
import unittest
from unittest.mock import patch
# --- code under test (normally in another module) ---
from time import monotonic # imported name: patch it HERE, in this module
def elapsed_label(start):
secs = monotonic() - start
return "slow" if secs > 1 else "fast"
# ----------------------------------------------------
class TestElapsed(unittest.TestCase):
@patch(f"{__name__}.monotonic", return_value=105.0)
def test_slow(self, fake_monotonic):
self.assertEqual(elapsed_label(100.0), "slow")
fake_monotonic.assert_called_once()
def test_wrong_place(self):
with patch("time.monotonic", return_value=105.0) as fake:
elapsed_label(100.0)
fake.assert_not_called() # our module kept its own reference
unittest.main(exit=False) # Ran 2 tests ... OKRemember
- patch replaces an object for one test and restores it
- Patch where the name is looked up, not where it's defined
- patch.object and patch.dict cover attributes and dictionaries
Watch out for
- Patching 'requests.get' when the module did 'from requests import get'; patch 'mymodule.get' instead.