Mocking in Python: unittest.mock, patch and the Mistakes That Make Tests Lie
Mocks let you test code without a real network, database or clock. Used carelessly, they also let broken code pass. Here is how to use unittest.mock well, with every example run.
- A mock stands in for a real object, such as an API client, so tests run fast and offline.
- Use
return_valuefor a fixed answer andside_effectfor errors or a sequence. - Patch the name where it is used, not where it is defined. This is the most common bug.
- Use
specorautospecso typos and wrong arguments fail instead of passing silently.
Your function calls a weather API. Your test should not depend on the internet, an API key or tomorrow’s weather. Python’s built-in unittest.mock lets you replace that call with a stand-in that returns exactly what you choose.
We wrote a small weather module and nine tests, all run with Python 3.13’s unittest. All nine passed, and the printed outputs below come from that run.
Why mock?
- Speed: no network calls or database setup.
- Reliability: tests do not fail because a third-party service is down.
- Control: you can simulate timeouts, errors and odd data on demand.
- Isolation: you test your logic, not someone else’s server.
Mocks belong mainly in unit tests. Integration tests should still talk to real, or realistic, dependencies.
Mock basics
from unittest.mock import Mock
api = Mock()
api.get_price.return_value = 499
print(api.get_price("pen")) # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list) # [call('pen')]
print(api.anything_at_all()) # <Mock name='mock.anything_at_all()' ...>
A Mock records every call, so you can check how it was used. Notice the last line, though. A plain Mock invents any attribute you ask for. That flexibility is convenient, and also dangerous, as we will see.
MagicMock is the same idea but also supports “magic” methods like __len__ and __iter__. patch() uses MagicMock by default.
side_effect: errors and sequences
side_effect controls what happens on each call. Set it to an exception to simulate a failure:
api.fetch.side_effect = ConnectionError("down")
api.fetch() # raises ConnectionError
Set it to a list to return different results on successive calls. This is perfect for testing retry logic:
def fetch_with_retry(api, attempts=3):
for _ in range(attempts):
try:
return api.fetch()
except ConnectionError:
continue
raise ConnectionError("all attempts failed")
api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api)) # {'ok': True}
print(api.fetch.call_count) # 2
The first call failed, the second succeeded, and the test proved the function retried exactly once. Simulating this against a real service would be close to impossible.
patch(): replace something temporarily
Often the dependency is not passed in. It is imported inside the module. patch() swaps it out for the duration of a test and restores it afterwards:
# weather.py
from http_client import get as requests_get
def report(city):
data = requests_get(f"https://api.example.com/weather?city={city}")
return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch
with patch("weather.requests_get") as fake_get:
fake_get.return_value = {"temp_c": 31}
print(weather.report("Pune")) # Pune: 31°C
fake_get.assert_called_once_with(
"https://api.example.com/weather?city=Pune")
You can use patch as a context manager like this, or as a decorator, which passes the mock in as an argument.
Patch where it is used, not where it is defined
This is the mistake almost everyone makes once. The real get function lives in http_client, so it feels natural to patch it there:
with patch("http_client.get") as fake_get:
fake_get.return_value = {"temp_c": 99}
weather.report("Pune")
# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune
The real function ran. When weather.py did from http_client import get as requests_get, it created its own name pointing at the original function. Patching http_client.get changed a different name, so weather never saw the mock. Our http_client.get raises on purpose, so the mistake was loud. In a real project, it might quietly call a live API.
Rule: patch the name in the module under test, written as "module_under_test.name". If the module did import http_client and calls http_client.get(), then patching "http_client.get" is correct, because the lookup happens at call time.
spec and autospec: make mocks strict
A plain Mock accepts anything, including typos. With a spec, it only allows attributes the real class has:
api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'
loose = Mock()
loose.get_temprature("Pune") # returns a Mock, no error at all
The loose mock happily accepted the misspelt method, so a test using it could pass even though the real code would crash. autospec goes further and checks method signatures:
with patch("weather.WeatherClient", autospec=True) as Fake:
client = Fake.return_value
client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments
Prefer autospec=True or create_autospec() when mocking your own classes. It keeps mocks honest as the real code changes.
Mocking dates and time
Code that depends on today’s date is hard to test. Wrap the clock in a small function and patch that:
def today():
return datetime.date.today()
def is_weekend():
return today().weekday() >= 5
@patch("weather.today")
def test_date(self, fake_today):
fake_today.return_value = datetime.date(2026, 11, 7)
print(weather.is_weekend()) # True (a Saturday)
You cannot easily patch datetime.date.today directly, because built-in types do not allow their attributes to be set. Libraries such as freezegun and time-machine handle this more fully.
Assertions on mocks
| Method | Checks |
|---|---|
assert_called() |
Called at least once |
assert_called_once() |
Called exactly once |
assert_called_with(...) |
The last call used these arguments |
assert_called_once_with(...) |
Exactly one call, with these arguments |
assert_any_call(...) |
Any call used these arguments |
assert_not_called() |
Never called |
When an assertion fails, the message is clear. We called assert_called_once() on a method that never ran:
AssertionError: Expected 'save' to have been called once. Called 0 times.
Older Python versions let misspelt assertion methods, such as assret_called_once, silently pass. Modern versions raise an error for names starting with assert or common misspellings of it, but a spec is still the stronger guard.
pytest and mocks
Everything above works in pytest too. Many pytest users also use the built-in monkeypatch fixture for simple replacements, or the pytest-mock plugin, which provides a mocker fixture that undoes patches automatically. The “patch where it is used” rule applies to all of them. See our pytest tutorial for fixtures and setup.
When not to mock
- Do not mock what you are testing. If the mock does all the work, the test proves nothing.
- Avoid mocking simple value objects. Use the real thing.
- Do not mock everything. Tests full of mocks often break whenever the code is refactored, even when behaviour is correct.
- Keep some real integration tests. Mocks encode your assumptions about a service, and those assumptions can be wrong.
Mocks also inflate code coverage easily, since mocked paths run without any real behaviour behind them.
Common mocking mistakes
- Patching the wrong name. Covered above, and still the number one cause of “my mock is ignored”.
- Forgetting to set return_value. The code under test then receives a Mock object instead of data. It may pass straight through your function and make a test pass by accident, for example when you only check that a result is truthy.
- Asserting on the mock instead of the outcome. Checking that
save()was called is weaker than checking what was saved. Useassert_called_once_with(...)with real arguments, or better, assert on the function’s result. - Leaking patches. Calling
patcher.start()withoutstop()leaves the fake in place for later tests. Use the context manager, the decorator oraddCleanup(patcher.stop). - Stacked decorators in the wrong order. With several
@patchdecorators, the bottom one is passed as the first argument. Mixing them up silently configures the wrong mock. - Mocking a library you do not own across many tests. Wrap it in a small class of your own and mock that instead. Your tests then describe your needs, not the library’s API.
Mocks, stubs and fakes
These words are often used loosely, but the differences help when discussing tests. A stub just returns canned answers. A mock also records calls so you can verify how it was used. A fake is a working, simplified implementation, such as an in-memory repository instead of a database. Python’s Mock can act as a stub or a mock. Fakes are ordinary classes you write yourself, and they often lead to tests that survive refactoring better, because they check behaviour rather than exact calls.
A good rule of thumb is to mock at the edges of your system: the network, the clock, random numbers, the file system and paid third-party services. Inside those edges, prefer real objects. Tests built this way stay fast, and they still exercise the code you actually wrote.
Final thoughts
Mocking is a precise tool: replace slow or unpredictable dependencies, control what they return, and check how your code used them. Two habits prevent most problems. Patch the name in the module under test, and use autospec so your mocks reject calls the real object would reject.
Frequently asked questions
What is mocking in Python?
Mocking replaces a real object, such as an API client or database, with a fake that you control during a test. Python includes this in the unittest.mock module.
What is the difference between Mock and MagicMock?
MagicMock is a Mock that also supports magic methods such as __len__, __iter__ and __enter__. patch() creates a MagicMock by default.
Why is my patch not working?
Usually because you patched where the object is defined instead of where it is used. Patch the name inside the module under test.
What is the difference between return_value and side_effect?
return_value sets a fixed result. side_effect can raise an exception, return values from a list in order, or call a function.
What does autospec do?
It builds a mock from the real object, so missing attributes and wrong arguments raise errors, just as they would with the real code.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.