Filter concepts by levelShowing all levels.

Django · Mocking

Mocking

Mock, MagicMock, patch and AsyncMock; stubs, fakes and spies; and where the boundary belongs

  • Patch where the name is USEDfrom x import y copies the reference into the importing module
  • autospec=True is the default you wantwithout it, a wrong signature or a fabricated method passes silently
  • Prefer the least specific doublefakes assert outcomes; strict mocks assert implementation
  • Never mock the ORMquery construction is where the bugs are, and a mock agrees with whatever you wrote
  • Assert outcomes, not call sequencesa behaviour-preserving refactor should not turn the suite red

1.Mock, MagicMock, patch, AsyncMock, and monkeypatching

`Mock` is an object that accepts any attribute access and any call, records what happened, and returns another `Mock`. `MagicMock` is the same with the dunder methods configured, so it supports `len()`, iteration, `in`, and context managers — which is why it is what `patch` gives you by default. `AsyncMock` returns a coroutine when called, so it stands in for anything you `await`. `patch` temporarily replaces an attribute for the duration of a test and puts the original back afterwards, and pytest's `monkeypatch` fixture does the same for attributes, dictionary entries and environment variables. The single rule that decides whether any of this works: **patch where the name is used, not where it is defined**.

Key features

  • `Mock` accepts any attribute and any call; modern Python raises for names starting with `assert`, but `called_once_with` (no prefix) silently passes — `autospec=True` is what catches it
  • `MagicMock` adds configured dunders (`__len__`, `__iter__`, `__enter__`, …), which is why `patch` returns one by default
  • `AsyncMock` returns a coroutine when called; patching an async function with a plain `Mock` gives a `TypeError` about awaiting a `Mock`
  • Patch where the name is **used**, not where it is defined: `from x import y` copies the reference into the importing module
  • `patch` works as a decorator, a context manager, or `start()`/`stop()`; all three restore the original afterwards
  • pytest's `monkeypatch` covers `setattr`, `setitem`, `setenv` and `delattr` with automatic teardown — better than `patch` for environment and dict entries

Which double, and when

Which double, and when
ClassAddsUse for
`Mock`attribute and call recordinga plain collaborator
`MagicMock`configured dunder methodsanything used with `len`, `in`, `for`, or `with`
`AsyncMock`calls return coroutinesanything you `await`
`patch(..., autospec=True)`the real signaturealways — it catches wrong calls
`monkeypatch`env vars, dict items, teardownsettings, `os.environ`, module-level dicts
Every part of a patch call, and what it decides

@patch("orders.services.charge_card", autospec=True, return_value="ch_1")

@patch

temporary, and self-undoing — Replaces the attribute for the duration of the test and restores it afterwards, whether the test passes, fails, or raises.

"orders.services

the module where the name is LOOKED UP — Not where the function is defined. `from billing import charge_card` copies the reference into orders.services, so that is the namespace to patch.

.charge_card"

the attribute being replaced — A string, resolved at call time — which is why a typo here silently patches nothing unless autospec catches it.

autospec=True

the guardrail — Builds the double from the real signature, so a wrong argument count or a nonexistent method raises instead of quietly returning another Mock.

return_value="ch_1"

what the call answers — Without it the call returns a Mock, which is truthy and often passes an assertion by accident. Use side_effect instead to raise or to vary per call.

  • Whole: @patch("orders.services.charge_card", autospec=True, return_value="ch_1")
  • @patch — temporary, and self-undoing: Replaces the attribute for the duration of the test and restores it afterwards, whether the test passes, fails, or raises.
  • "orders.services — the module where the name is LOOKED UP: Not where the function is defined. `from billing import charge_card` copies the reference into orders.services, so that is the namespace to patch.
  • .charge_card" — the attribute being replaced: A string, resolved at call time — which is why a typo here silently patches nothing unless autospec catches it.
  • autospec=True — the guardrail: Builds the double from the real signature, so a wrong argument count or a nonexistent method raises instead of quietly returning another Mock.
  • return_value="ch_1" — what the call answers: Without it the call returns a Mock, which is truthy and often passes an assertion by accident. Use side_effect instead to raise or to vary per call.

Command

python
with patch("orders.views.charge_card", autospec=True) as charge:
    charge.side_effect = PaymentDeclined("insufficient funds")
    ...

`Mock` says yes to everything, which is why a mistyped assertion silently passes — use `autospec=True` as the default so a wrong signature or a fabricated method raises. `MagicMock` adds dunders, `AsyncMock` returns coroutines and is counted with `await_count`. Patch where the name is **used**, because `from x import y` copies the reference into the importing module. And prefer `monkeypatch` for environment variables and dictionary entries, where its teardown is cleaner than `patch.dict`.

2.Stubs, fakes, spies, and the test-double vocabulary

"Test double" is the umbrella term for anything standing in for a real collaborator, and the varieties differ in how much they do. A **stub** returns canned answers and nothing more — it exists to make the code under test proceed. A **fake** is a working implementation that is unsuitable for production: an in-memory dictionary instead of Redis, a list instead of an outbox. A **spy** wraps the real thing and records how it was called, so behaviour is unchanged and you can still assert on the interaction. A **mock** is a double with expectations built in — it knows what it should be called with and fails if it is not. Python's `Mock` can play all four roles, which is why the vocabulary is worth keeping even though the class is the same.

Key features

  • Stub: canned return values, no assertions — `Mock(return_value=…)` or a small hand-written class
  • Fake: a real, working implementation unfit for production — an in-memory store, `locmem` email, `LocMemCache`
  • Spy: wraps the real object and records calls — `patch(..., wraps=real_object)` or `mocker.spy`
  • Mock: a double with expectations; `assert_called_once_with` turns the interaction itself into the assertion
  • Django already ships fakes you use daily: `locmem` email backend (`mail.outbox`) and `LocMemCache`
  • Prefer the least specific double that can observe the outcome — a strict call-order mock fails on refactors that change nothing observable

The four doubles, by how much they constrain the test

The four doubles, by how much they constrain the test
DoubleBehaviourAssertion is aboutBreaks on
Stubcanned answersthe outcomealmost nothing
Fakea real, simplified implementationthe outcomea genuine behaviour change
Spythe real thing, recordedthe outcome and the calla real behaviour change
Mockcanned answers + expectationsthe interactionany refactor of how it is called
Four doubles, and what each one lets a test see

Stub and Fake

Stub: canned answers

just enough for the code to proceed

Fake: a working simplified version

an in-memory dict, LocMemCache, locmem email

Survives refactors

the test is about results, not about calls

Spy

patch(..., wraps=real)

the real call still happens

Assert on both

the outcome AND that it was called

Only when the real call is safe

cheap, deterministic, no side effect you mind

Mock

Right when the call is the requirement

"charge the card exactly once"

Wrong everywhere else

a refactor with identical results fails the test

Use the least specific double that works

reach for mock last, not first

  • A collaborator you do not want to call for real
  • Stub and Fake — assert on the outcome
    • Stub: canned answers — just enough for the code to proceed
    • Fake: a working simplified version — an in-memory dict, LocMemCache, locmem email
    • Survives refactors — the test is about results, not about calls
  • Spy — real behaviour, plus a record
    • patch(..., wraps=real) — the real call still happens
    • Assert on both — the outcome AND that it was called
    • Only when the real call is safe — cheap, deterministic, no side effect you mind
  • Mock — the interaction IS the assertion
    • Right when the call is the requirement — "charge the card exactly once"
    • Wrong everywhere else — a refactor with identical results fails the test
    • Use the least specific double that works — reach for mock last, not first

Command

python
class FakeCache:                       # a fake: small, real, in-memory
    def __init__(self): self._d = {}
    def get(self, k, default=None): return self._d.get(k, default)
    def set(self, k, v, timeout=None): self._d[k] = v

Stub returns canned answers, fake is a working simplified implementation, spy wraps the real thing and records, mock carries expectations. They form a scale of how much the test depends on *how* the code works: fakes and stubs assert on outcomes and survive refactors, while a strict mock asserts the interaction and is right only when the interaction is the requirement. Reach for the least specific double that can observe what you care about — and remember Django already ships two fakes you rely on, `locmem` email and `LocMemCache`.

3.What to mock, what not to, and behaviour over implementation

Mock at the boundaries of your system and nowhere else. **Mock**: anything that leaves the process (a payment provider, an SMTP server, an SMS gateway), anything nondeterministic (the clock, `random`, `uuid4`), and anything slow or expensive that you do not own. **Do not mock**: your own ORM, your own models, your own service functions, or the database — those are the things the test exists to exercise, and replacing them means the test checks that your mocks were configured correctly. And mock *behaviour*, not implementation: assert that a charge was made, not that a particular private helper was called in a particular order, or a refactor that changes nothing observable will fail.

Key features

  • Mock anything that leaves the process — HTTP calls, SMTP, SMS, object storage, third-party SDKs
  • Mock anything nondeterministic — `timezone.now()`, `random`, `uuid4()` — so a test cannot fail on a Tuesday
  • Do **not** mock the ORM, your own models, or your own service layer: those are what the test exists to exercise
  • Do not mock the database — Django's test database with rollback isolation is fast enough and infinitely more truthful
  • Assert on observable outcomes (a row, a response, an outbox) rather than on which internal helpers were called
  • The exception where interaction assertions belong: when the call itself is the requirement, such as charging a card exactly once

Mock it, or not?

Mock it, or not?
DependencyMock?Why
A payment provider, an SMS gatewayyesleaves the process; costs money; not yours
`timezone.now()`, `random`, `uuid4()`yesnondeterministic — freeze it instead of hoping
An email backendno — use the fakeDjango ships `locmem`; `mail.outbox` is better than a mock
The cacheno — use `LocMemCache`a real backend, and the test then proves caching works
Your ORM / models**no**this is what the test is for
Your own service functions**no**mocking them tests your mock configuration
The database**no**the test database is fast and truthful
Mock by distance from your code, not by convenience
Your models and ORM
never mock — this is the thing under test
Your service functions
never mock — mocking them tests your mock setup
The test database
real, with rollback isolation. Fast enough and far more truthful.
Email / cache
use Django's shipped fakes: locmem and LocMemCache
timezone.now(), uuid4()
freeze it — a test must not fail on a Tuesday
Payment provider, SMS, S3
mock at this boundary and nowhere closer in
  • Your models and ORM: your code owns it, deterministic and fast — never mock — this is the thing under test
  • Your service functions: your code owns it, deterministic and fast — never mock — mocking them tests your mock setup
  • The test database: your code owns it, between deterministic and fast and slow, costly, or nondeterministic — real, with rollback isolation. Fast enough and far more truthful.
  • Email / cache: between your code owns it and someone else owns it, between deterministic and fast and slow, costly, or nondeterministic — use Django's shipped fakes: locmem and LocMemCache
  • timezone.now(), uuid4(): between your code owns it and someone else owns it, slow, costly, or nondeterministic — freeze it — a test must not fail on a Tuesday
  • Payment provider, SMS, S3: someone else owns it, slow, costly, or nondeterministic — mock at this boundary and nowhere closer in

Command

python
with freeze_time("2026-09-04T10:00:00Z"), \
     patch("orders.services.charge_card", autospec=True):
    place_order(order)          # everything inside the boundary stays real

Mock at the boundary: anything leaving the process, and anything nondeterministic. Never mock your own ORM, models, services or the database — those are the things the test exists to exercise, and replacing them means the test verifies your mock configuration instead. Assert on observable outcomes rather than on which internal helpers were called, so a behaviour-preserving refactor stays green. And take the section's warning literally: a test that passes only because everything is mocked provides very little confidence.

Remember

  • AsyncMock for anything awaited, and assert on await_count — a coroutine created but never awaited is a real bug that call_count would hide.
  • monkeypatch is cleaner than patch for environment variables and dict entries, and undoes itself the same way.
  • Django already ships two fakes you rely on daily: the locmem email backend behind mail.outbox, and LocMemCache.
  • Mock what leaves the process and what is nondeterministic; everything inside the boundary — ORM, transaction, signals — stays real.

Pro tip

  • The dangerous typo is the one that drops the assert_ prefix: modern mock raises for attributes starting with "assert", but charge.called_once_with(...) silently passes.
  • Reserve interaction assertions for cases where the interaction IS the requirement — "charge the card exactly once" qualifies; "call the validator first" does not.

Next up

  • Django API Testing
  • Database Testing
  • Contract and Integration Testing

Sources

  • unittest.mock — docs.python.org/3/library/unittest.mock.html
  • Every claim checked against Python 3.12 docs · checked 2026-09-04

Spotted a mistake on this page? Tell us.