AlgoMaster Logo

Mocking with unittest.mock

Medium Priority33 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

Real tests can't make HTTP calls, charge credit cards, or send emails every time they run. They'd be slow, flaky, and expensive. unittest.mock (part of the standard library, used by both unittest and pytest) lets you replace those external dependencies with fake objects that you control, so your tests stay fast and produce the same result every time you run them.

Why Mocking Matters

Consider this function that processes an order. It calls a payment gateway (the external service that processes a credit card charge), sends a confirmation email, and updates an inventory API.

Testing this for real means hitting three live services every time. The tests would be slow (network round trips), flaky (any service hiccup fails the test), expensive (real charges and emails), and impossible to run offline. You also can't easily test the "payment declined" branch because you'd need a credit card that's set up to fail.

Mocking replaces those network calls with stand-in objects. The test runs in microseconds, never hits the network, and you can dictate exactly what each fake response should look like, including failures.

No real HTTP calls. The test ran offline. We pinned down exactly which response the fake should return. That's the whole point of mocking.

Mock vs MagicMock

unittest.mock ships two main mock classes: Mock and MagicMock. They're almost the same, with one important difference.

A Mock records every call made on it and lets you query that later. It also auto-creates attributes on access, so m.anything.you.want() works without complaint. What Mock does not do is implement Python's dunder methods. If your code under test uses len(obj), obj[0], obj + 1, or with obj:, a plain Mock will blow up.

MagicMock fixes that by pre-configuring all the dunder methods (__len__, __getitem__, __iter__, __enter__, __exit__, and so on) to return sensible defaults.

Use MagicMock by default. It costs the same and avoids surprises when the code under test uses dunder methods. Reach for plain Mock only when you specifically want len() or with to raise (to verify your code doesn't use them), which is rare.

FeatureMockMagicMock
Records callsYesYes
Auto-creates attributesYesYes
Supports len(), iter(), with, []NoYes
Default to useRareMost of the time

Configuring Return Values

A bare mock returns a child mock from every call. That's almost never what you want for tests. You usually want the mock to return a specific value when called.

The return_value attribute pins down what the mock returns when called. Every call from now on returns the same value, no matter what arguments you pass.

Every call returns 42 because return_value is a single fixed value. If you want different return values for different calls, you need side_effect.

Side Effects: Exceptions, Sequences, and Functions

side_effect is the more powerful sibling of return_value. It can do three different things depending on what you assign to it.

Raising an Exception

Assign an exception (or exception class) to side_effect and the mock raises it when called.

This is how you test the failure branches of your code. Want to verify that your retry logic kicks in when the payment gateway times out? Make the mock raise TimeoutError and watch what your function does.

Returning a Sequence

Assign a list (or any iterable) to side_effect and the mock returns each item in turn. The first call gets the first item, the second call gets the second, and so on. When the iterable runs out, the next call raises StopIteration.

This is useful for testing retry loops: make the first two calls fail and the third succeed.

Items in the list that are exception instances get raised; everything else gets returned. You can mix both freely.

Calling a Function

Assign a function to side_effect and the mock calls that function with the same arguments and returns whatever the function returns. This is the right tool when the return value depends on the input arguments.

The mock still records every call (you can assert on call_args_list), but the return value comes from the function. Use this when "fixed value" or "fixed sequence" isn't expressive enough.

Assigned to side_effectBehavior
Exception instance or classMock raises it on every call
List or iterableMock returns items one by one, raises StopIteration when exhausted
FunctionMock calls the function with the same arguments and returns its result
None (default)No side effect; the mock returns return_value

Assertion API

Recording calls is only useful if you can assert on what happened. Mock and MagicMock ship a family of assertion methods plus some inspection attributes.

Here's the full set worth knowing, in roughly the order you'll reach for them:

Method or attributeWhat it checks
assert_called()The mock was called at least once
assert_called_once()The mock was called exactly once
assert_called_with(*args, **kwargs)The last call matched these arguments
assert_called_once_with(*args, **kwargs)Called exactly once with these arguments
assert_any_call(*args, **kwargs)At least one of the calls used these arguments
assert_not_called()The mock was never called
call_countNumber of times the mock was called (integer)
call_argsThe arguments of the last call (a call(...) object)
call_args_listAll calls in order, as a list of call(...) objects
calledBoolean: was it called at least once

The naming trips up a lot of people. assert_called_with checks the last call, not whether the mock was ever called with those arguments. For "ever called with X", use assert_any_call.

The last call was to Bob, so assert_called_with(to="alice@example.com") fails even though Alice was called earlier. Use assert_any_call for that.

patch: Decorator, Context Manager, and Manual

MagicMock is useful on its own, but most real tests need to replace a name in some other module with a mock. That's what patch does. It temporarily swaps an attribute for a mock and puts the original back when you're done. There are three ways to use it.

As a Decorator

@patch("requests.get") replaces requests.get with a MagicMock for the duration of the test, then puts the real requests.get back. The mock gets injected as the first parameter to the test function.

If you stack multiple @patch decorators, the arguments come in bottom-up order. The decorator closest to the function gets the first parameter.

The closer the decorator is to the function, the earlier its mock appears in the argument list. This is the most common source of "wait, why does this mock have the wrong methods" confusion.

As a Context Manager

When you only need to patch something for part of a test, or when you're not writing a function (such as in an interactive script), use patch as a context manager.

The with block scopes the patch tightly. Once you exit, requests.get is the real one again. Use this when you want fine-grained control over which lines see the mock.

Manual Start and Stop

For setup/teardown patterns (like setUp/tearDown in unittest, or pytest fixtures that aren't using monkeypatch), call start() and stop() directly.

The manual form is the most verbose and the easiest to forget to clean up. Always pair start() with stop(), and prefer the decorator or context manager forms unless you specifically need the start/stop split.

patch.object: Patching a Specific Attribute

patch("some.module.name") patches by string lookup. Sometimes you already have the object in hand and want to patch one of its attributes. That's what patch.object is for.

The first argument is the object, the second is the attribute name as a string. This is handy when you've already imported the module and don't want to spell out the full dotted path.

patch.object shines for patching instance attributes too:

Inside the with, gateway.charge is the mock and returns the declined response. Outside, the real method is back. Notice you can pass return_value= directly to patch.object as a keyword argument to skip the separate assignment.

The "Where to Patch" Gotcha

This is the single most common mistake people make with patch. The rule sounds simple but it's easy to get wrong.

Patch where the name is looked up, not where it's defined.

To see why, imagine an orders.py module that uses requests to charge a customer:

When orders.py runs import requests, Python binds the name requests inside the orders module's namespace. The line requests.post(...) looks up requests in orders.__dict__, not in some global place. So if you patch requests.post, you've only changed the requests module's post attribute, but orders.requests still has the original reference if orders.py did from requests import post instead of import requests.

The diagram below shows the two namespaces involved:

The diagram has two paths: the source module (requests) where the function lives, and the importer module (orders) where the function is used. patch has to target the namespace that the code under test actually looks up.

A concrete example. Two ways to import:

For orders_a.py, both of these work:

For orders_b.py, you must patch orders_b.post, not requests.post:

Here's a working demonstration. We'll define two small modules inline and patch each one correctly.

orders_a works because requests.post is looked up every time place_order runs (it does requests.post(...)). orders_b already captured a reference to post at import time, so patching requests.post later has no effect on the name orders_b.post. To fix it, patch orders_b.post directly.

The rule again: patch the name where the code under test looks it up. If you ever get a confusing test result where the mock didn't seem to apply, this is almost certainly the cause.

autospec and create_autospec

A mock will happily accept any attribute access and any call signature. That's flexible, but it means typos in your test code go unnoticed.

The mock doesn't know that chrage isn't a real method, so it cheerfully creates a child mock and lets the test pass. The same problem happens with argument signatures: you can pass any keyword to a mocked method, even if the real method doesn't accept it.

autospec=True (an option for patch) and create_autospec (a standalone function) build mocks that mirror the signature of the real thing. Attribute access for missing names raises AttributeError, and calls with the wrong signature raise TypeError.

Now the typo and the wrong signature both fail loudly. That's exactly what you want from tests.

The patch decorator also accepts autospec=True, which is the easiest way to get a strict mock for the duration of a test.

Use autospec=True whenever the thing you're patching has a stable interface. The extra strictness catches refactoring bugs: if you rename charge to process_payment but forget to update the test, autospec fails loudly instead of silently passing.

OptionCatches typos in attribute namesCatches wrong call signaturesSetup cost
MagicMock()NoNoNone
MagicMock(spec=Klass)YesNoPass the class
create_autospec(Klass)YesYesOne function call
patch(..., autospec=True)YesYesOne keyword arg

spec vs autospec

spec and autospec solve overlapping problems, and the difference is worth knowing.

MagicMock(spec=Klass) limits the mock to the attribute names of the class. It doesn't check call signatures.

create_autospec(Klass) (or patch(..., autospec=True)) is stricter: it checks both attribute names and call signatures.

Prefer autospec when the target has a stable signature you want to enforce. Fall back to spec when you need the attribute-name check but want the signature flexibility (rare in practice).

Mocking Properties, Attributes, Context Managers, and Async

The patterns above cover regular method calls. A few special cases need slightly different recipes.

Properties

Setting mock.some_property = value works for plain attributes, but Python properties on a class have to be patched with PropertyMock.

PropertyMock has to be attached to the type of the mock (type(mock_order)), not the instance, because that's how Python looks up descriptors. PropertyMock also tracks how many times the property was accessed, which is sometimes useful.

Plain Attributes

For non-property attributes, just assign them directly:

Context Managers

MagicMock already supports with, so mocking a context manager is straightforward. The trick is that the value bound to as comes from __enter__.return_value.

Async Functions

Python 3.8 added AsyncMock for mocking async def functions and coroutines. It's like MagicMock but the call returns an awaitable.

AsyncMock also has await_count, await_args, await_args_list, and assert_awaited* family of assertions that mirror the sync ones. Use AsyncMock (not MagicMock) whenever the thing under test calls await mock(...).

pytest's monkeypatch: A Lighter Alternative

pytest ships a built-in fixture called monkeypatch that does the same kind of "swap something out, put it back when the test ends" job as unittest.mock.patch. It's lighter weight, doesn't require importing unittest.mock, and is the idiomatic choice in pytest tests for simple swaps. monkeypatch doesn't build a Mock. It just sets an attribute and remembers to restore it.

The monkeypatch fixture exposes these helpers (the most common ones):

HelperWhat it does
monkeypatch.setattr(target, name, value)Set an attribute, restore on teardown
monkeypatch.delattr(target, name)Delete an attribute, restore on teardown
monkeypatch.setenv(name, value)Set an environment variable
monkeypatch.delenv(name)Delete an environment variable
monkeypatch.setitem(d, key, value)Set a dict item, restore on teardown
monkeypatch.delitem(d, key)Delete a dict item, restore on teardown
monkeypatch.chdir(path)Change working directory, restore on teardown
monkeypatch.syspath_prepend(path)Prepend to sys.path, restore on teardown

monkeypatch.setattr accepts the same "dotted string" form as patch:

The choice between monkeypatch and patch is mostly about scale of needs.

Reach for `monkeypatch` when:

  • You just need to swap in a function or a value
  • You need to set environment variables or dict entries
  • You're already writing pytest tests (the fixture is right there)

Reach for `patch` (with `MagicMock`) when:

  • You need a mock that records calls (call_args_list, assert_called_with)
  • You want autospec to enforce signatures
  • You're not using pytest (so the monkeypatch fixture isn't available)
  • You need side_effect for sequences, exceptions, or functions

Here's the same kind of test written both ways for comparison:

The monkeypatch version is shorter and reads like "set this, the test will handle the rest". The patch version gives you the mock_get handle so you can assert on the calls. Pick whichever you need; mixing both in the same project is fine and common.

Over-Mocking: The Brittleness Trap

Mocks are powerful, and that's the problem. If you mock too much, your tests start to look like they pass while the underlying code is broken. A test that mocks every collaborator only proves that the code calls those collaborators in the order you expected, not that the system actually works.

Two rules of thumb keep over-mocking in check:

Don't mock what you don't own. Wrap third-party libraries (HTTP clients, database drivers, payment SDKs) in your own thin adapter layer, and mock the adapter, not the library. The library's interface might change between versions; your adapter is yours to keep stable.

In tests, mock PaymentClient.charge, not requests.post. If requests ever changes its API, only the adapter needs updating; the tests stay valid.

Don't mock value objects or pure functions. Things like dataclasses, datetime arithmetic, or pure utility functions don't need mocking. They're fast, deterministic, and don't reach outside the process. Mocking them adds noise and decouples your test from real behavior.

The watchword is "mock at the boundaries": where your code talks to something slow, expensive, non-deterministic, or outside your control. Everything inside that boundary stays real.

A Realistic Test: Order Placement with Mocks

To pull everything together, here's a test for the place_order function from earlier. It exercises return_value, side_effect, multiple patches, and the call assertions.

Three branches, three tests, no real network calls. The "declined" case shows that the email isn't sent when the charge fails (call_count is 1, not 2). The "raised" case shows the connection error propagates correctly. This is what you get from mocking at the right boundary.

Quiz

Mocking Quiz

10 quizzes