Filter concepts by levelShowing all levels.

Python · Mocking

Mocking

Mock, MagicMock, patch, and monkeypatch — plus the discipline of mocking the boundary, never the thing under test

  • Mock() / MagicMock()records calls, returns a configured value
  • patch(...)temporarily swaps a name, restores it automatically
  • AsyncMockthe awaitable version, for mocking coroutines
  • Mock the boundarynever mock the code the test exists to check

1.Mock and MagicMock

Mock() creates a stand-in object: any attribute or method call on it just returns another Mock, and it records every call so a test can check it later. MagicMock is the same, but also supports dunder methods like __len__ and __iter__.

Key features

  • Mock() auto-creates any attribute/method accessed on it — no advance definition needed
  • Mock(return_value=X) makes calling the mock always return X
  • mock.assert_called_once_with(args) checks it was called exactly once, with those args
  • mock.call_count and mock.call_args_list record how often and with what args it was called
  • MagicMock supports dunder methods (__len__, __iter__, __enter__) plain Mock does not

Mock — assertions worth knowing

Mock — assertions worth knowing
CallChecks
mock.assert_called()was called at least once
mock.assert_called_once()was called exactly once
mock.assert_called_once_with(*a, **kw)called exactly once, with these exact arguments
mock.assert_not_called()was never called
mock.call_countthe number of times it was called, as an int
Mock vs. MagicMock — one extra capability

Mock()

records calls, auto-creates attributes

assert_called_once_with(...)

reads back what was recorded

MagicMock()

same, plus __len__, __iter__, __enter__

  • Mock() — records calls, auto-creates attributes
  • assert_called_once_with(...) — reads back what was recorded
  • MagicMock() — same, plus __len__, __iter__, __enter__

Command

python
from unittest.mock import Mock, MagicMock

m = Mock(return_value=42)
m(1, 2)                       # returns 42
m.assert_called_once_with(1, 2)

mm = MagicMock()
mm.__len__.return_value = 5
len(mm)                        # 5 -- plain Mock has no __len__

Mock() records calls and returns whatever you configure; MagicMock() does the same plus supports dunder methods like __len__ that plain Mock does not.

2.patch and AsyncMock

patch("module.name") temporarily replaces a name in a module's namespace with a Mock, for the duration of a with block or test, then restores the original automatically. AsyncMock is the version whose return value is itself awaitable.

Key features

  • patch("module.name") targets the name as looked up there, not where it was defined
  • Used as a context manager (with patch(...) as m:) or a decorator (@patch("module.name"))
  • The original object is restored automatically when the block/test ends, even on failure
  • AsyncMock() makes calling it return a coroutine that, when awaited, gives return_value
  • mock.assert_awaited_once_with(args) is AsyncMock's version, checking it was awaited

patch — the forms worth knowing

patch — the forms worth knowing
FormUse
with patch("mod.name") as m:scoped to the with block only
@patch("mod.name")scoped to the whole decorated test function
patch.object(obj, "method")patches one method on an already-imported object
AsyncMock(return_value=X)an awaitable mock — await mock() returns X
patch swaps a name out, then back — even on failure

with patch("mymodule.send_email")

name replaced with a Mock

test body runs against the Mock

real function restored

automatic — even if the test raised

  • with patch("mymodule.send_email")
    • leads to name replaced with a Mock
  • name replaced with a Mock
    • leads to test body runs against the Mock
  • test body runs against the Mock
    • leads to real function restored
  • real function restored — automatic — even if the test raised

Command

python
from unittest.mock import patch, AsyncMock

with patch("mymodule.send_email") as mocked:
    mocked.return_value = "sent"
    ...   # send_email is a Mock here, restored automatically after

fetcher.fetch = AsyncMock(return_value={"status": "ok"})
result = await fetcher.fetch(url)
fetcher.fetch.assert_awaited_once_with(url)

patch("module.name") swaps a name for a Mock and restores it; AsyncMock is the awaitable version.

3.Monkeypatching

monkeypatch is a built-in pytest fixture for temporarily changing something — an attribute, an environment variable, a dict entry — for the duration of one test, undoing the change automatically afterward, even if the test fails.

Key features

  • monkeypatch is a built-in fixture — no import needed, just add it as a test parameter
  • monkeypatch.setattr(obj, "name", value) temporarily replaces an attribute
  • monkeypatch.setenv("NAME", "value") / delenv("NAME") temporarily change env vars
  • monkeypatch.setitem(dict, key, value) temporarily changes a dict entry
  • Every change monkeypatch makes is automatically undone after the test, pass or fail

Command

python
def test_uses_env_var(monkeypatch):
    monkeypatch.setenv("API_KEY", "test-key-123")
    assert get_api_key() == "test-key-123"
    # API_KEY is restored to whatever it was before, automatically

monkeypatch is a built-in fixture for temporary changes that automatically undo after the test.

4.Test doubles: stubs, spies, and fakes

A stub returns canned answers and nothing more. A spy is a real (or wrapped) object that also records how it was called. A fake is a working, simplified implementation — like an in-memory dict standing in for a real database.

Key features

  • Stub: returns pre-programmed answers, records nothing — the simplest double
  • Spy: wraps a real object (or Mock) and records how it was called, forwarding to real work
  • Fake: a working, simplified implementation — an in-memory dict instead of a real database
  • Mock (unittest.mock) can act as any of the three, depending on how it is configured

The four kinds, by what they actually do

The four kinds, by what they actually do
DoubleDoes real work?Records calls?
StubNo — returns a fixed answerNo
SpyOften yes — wraps something realYes
FakeYes — a simplified real implementationNo (usually)
Mock (unittest.mock)No, unless configured toYes

Command

python
class FakeRepository:               # a working, simplified implementation
    def __init__(self):
        self._items = {}
    def save(self, item):
        self._items[item["id"]] = item
    def get(self, item_id):
        return self._items.get(item_id)

Stubs return canned answers; spies wrap and record real work; fakes are simplified-but-working implementations.

5.Knowing what should (and should not) be mocked

Mock external dependencies you do not control — network calls, databases, payment gateways, the clock. Do not mock the code you are actually testing — that proves nothing except that the mock does what you told it to.

Key features

  • Mock external dependencies: network calls, databases, payment gateways, email, the clock
  • Do not mock the function/class actually under test — that is what the test exists to check
  • Over-mocking implementation details lets a test pass even after a refactor breaks behavior
  • A test with too many mocks often means the code has too many hidden dependencies
  • If a test still passes after deleting the code it claims to test, its mocking went too far
Mock the boundary, never the thing under test

Mock external dependencies

  • Network calls, databases, payment gateways, the clock
  • Keeps tests fast, deterministic, no real side effects
  • The real logic under test still runs

Mocking the code under test

  • The function the test exists to check is replaced too
  • Passes even after that function is broken — proves nothing
  • A test that still passes after deleting the real code went too far
  • Mock external dependencies
    • Network calls, databases, payment gateways, the clock
    • Keeps tests fast, deterministic, no real side effects
    • The real logic under test still runs
  • Mocking the code under test
    • The function the test exists to check is replaced too
    • Passes even after that function is broken — proves nothing
    • A test that still passes after deleting the real code went too far

Command

python
# GOOD -- mock the external boundary
def test_places_order(mock_payment_gateway):
    place_order(cart, mock_payment_gateway)
    mock_payment_gateway.charge.assert_called_once()

# BAD -- mocks the thing actually under test
def test_calculate_total():
    calc = Mock()
    calc.calculate_total.return_value = 100
    assert calc.calculate_total() == 100   # tests nothing real

Mock external boundaries (network, database, payment, clock) — never mock the code the test exists to actually check, or the test proves nothing.

Remember

  • Mock() records calls and returns a configured value; MagicMock() adds dunder method support (__len__, __iter__).
  • patch("module.name") temporarily swaps a name for a Mock, restoring the original automatically afterward.
  • AsyncMock is the awaitable Mock variant — assert_awaited_once_with checks it was actually awaited.
  • Mock external dependencies (network, database, payment); never mock the code the test exists to check.

Pro tip

  • monkeypatch (a built-in fixture) undoes every change automatically — prefer it over manual save/restore.
  • A fake (working, simplified implementation) is often better than a Mock when a test needs real behavior.
  • patch's target is the name AS LOOKED UP in the module using it, not where the function was originally defined.
  • If a test still passes after deleting the code it claims to test, its mocking has gone too far.

Next up

  • Quality

Sources

  • unittest.mock — docs.python.org/3/library/unittest.mock.html
  • Every example executed on Python 3.14.3 · checked 2026-08-21

Spotted a mistake on this page? Tell us.