## The problem that mocking solves
Start with this simple function that fetches the current temperature for a city:
```python
# weather.py
import requests
def get_temperature(city):
resp = requests.get(f'https://api.weather.example.com/temp/{city}')
return resp.json()['temperature']
```
Now write a test:
```python
def test_get_temperature():
result = get_temperature('Kyiv')
assert result == 22
```
This test has serious problems:
- **It fails if the internet is down** — even though your code is perfectly correct
- **It fails if the API changes its data** — `{'temperature': 22}` today, `{'temp': 22}` tomorrow
- **You cannot test the error path** — how do you make the API return a 500 error on demand?
- **It is slow** — a real HTTP call in every test run adds seconds
- **It is unpredictable** — the temperature changes; you can't write `assert result == 22`
The test is not testing *your* code — it is testing the weather API, the network, and the internet connection all at once.
---
## The core idea: a controlled stand-in
A **mock** is an object that impersonates the real dependency but is fully under your control. Instead of your code calling the real API, it calls a fake that you pre-programmed to return exactly the data you need.
Think of it like a controlled experiment. In chemistry, you don't test whether a pill works by giving it to random people in different environments — you control every variable so you can measure exactly one thing. A mock does the same: it removes all variables from the external dependency so your test measures only your code.
```
Real test (bad):
your code → requests → internet → real server → real data → your code
↑ any of these can fail unpredictably
Mocked test (good):
your code → mock → your data
↑ always fast, always returns what you say, always available
```
---
## Step 1 — MagicMock: your first fake object
`MagicMock` is the building block. It is an object that:
- Accepts **any method call** without raising `AttributeError`
- Returns another `MagicMock` from every call by default
- **Records every interaction** so you can check what happened after
```python
from unittest.mock import MagicMock
mock = MagicMock()
# Access any attribute — it just creates it automatically:
print(mock.name) # <MagicMock name='mock.name' id='...'>
print(mock.does_not_exist) # no AttributeError — auto-created
# Call any method — same thing:
result = mock.calculate(10, 5) # does not raise; returns another MagicMock
```
This "auto-create everything" behavior is why it's called *Magic*Mock. You never have to pre-declare what attributes or methods exist.
### Setting return values
Use `return_value` to control what a method returns when called:
```python
mock = MagicMock()
mock.calculate.return_value = 42
result = mock.calculate(10, 5)
print(result) # 42
```
### Inspecting what happened (the recording)
After calling methods on a mock, you can ask it exactly what happened:
```python
mock = MagicMock()
mock.send('hello', recipient='alice')
# Was it called at all?
mock.send.assert_called() # passes
# Was it called exactly once?
mock.send.assert_called_once() # passes
# Was it called with these exact arguments?
mock.send.assert_called_once_with('hello', recipient='alice') # passes
# What if we check the wrong arguments?
mock.send.assert_called_once_with('wrong') # AssertionError!
# How many times total?
print(mock.send.call_count) # 1
```
The recording is what makes mocks useful as *test spies* — you not only control what the dependency returns, you can verify your code called it correctly.
---
## Step 2 — Why MagicMock alone is not enough
You can create a perfect fake object, but there is a problem: your code already imported `requests` and holds a reference to it. Creating a new `MagicMock()` in your test does not affect what your function calls.
```python
# This does NOT work:
def test_get_temperature_broken():
fake_requests = MagicMock()
fake_requests.get.return_value.json.return_value = {'temperature': 22}
result = get_temperature('Kyiv') # still calls the REAL requests.get!
assert result == 22
```
Why? Because `weather.py` did `import requests` when it was first loaded. The name `requests` inside `weather.py` is bound to the real `requests` module. Your `fake_requests` variable in the test is just a local variable — it has no connection to what `weather.py` uses.
To make `get_temperature` use your fake, you need to replace the `requests` name *inside* the `weather` module. That is what `patch` does.
---
## Step 3 — patch: injecting the fake into the right place
`patch` temporarily replaces a name in a module's namespace with a `MagicMock` (or any object you choose), runs your test, then restores the original:
```python
from unittest.mock import patch
with patch('weather.requests') as mock_requests:
# Inside this block, weather.requests IS the mock
mock_requests.get.return_value.json.return_value = {'temperature': 22}
result = get_temperature('Kyiv')
assert result == 22
# After the block, weather.requests is the real requests module again
```
The string `'weather.requests'` is the address of the name to replace: *"go into the `weather` module and replace its `requests` attribute"*.
---
## Building a complete mock test, line by line
Here is the full test, explained step by step:
```python
from unittest.mock import patch
import weather
@patch('weather.requests.get') # (1)
def test_get_temperature(mock_get): # (2)
mock_get.return_value.json.return_value = {'temperature': 22} # (3)
result = weather.get_temperature('Kyiv') # (4)
assert result == 22 # (5)
mock_get.assert_called_once_with( # (6)
'https://api.weather.example.com/temp/Kyiv'
)
```
**(1)** `@patch('weather.requests.get')` — replace only `requests.get` inside the `weather` module (we patch the specific function, not the entire module).
**(2)** `def test_get_temperature(mock_get)` — pytest receives the `MagicMock` that replaced `requests.get` as the extra argument.
**(3)** `mock_get.return_value.json.return_value = {'temperature': 22}` — this is the trickiest line. Unpack it:
- `mock_get(...)` is our fake `requests.get(...)` — it returns `mock_get.return_value` (the fake response object)
- On that response object, `.json()` is called — so `mock_get.return_value.json` is the fake `.json` method
- `.return_value = {'temperature': 22}` makes `.json()` return our dict
In other words: `mock_get.return_value` = fake response, `.json.return_value` = what `.json()` returns.
**(4)** `weather.get_temperature('Kyiv')` — calls the real function. It will call `requests.get(...)` which is now our mock, get the fake response, call `.json()` on it, and return `22`.
**(5)** Assert the result is what we programmed.
**(6)** Assert our code actually called the API with the right URL — this verifies the function built the URL correctly, not just that it returned the right value.
### Visualizing the mock chain
```
weather.py calls: requests.get(url) → resp → resp.json() → {'temperature': 22}
Mock equivalent: mock_get(url)
↓ returns
mock_get.return_value (the fake response object)
↓ .json() called on it
mock_get.return_value.json()
↓ returns
mock_get.return_value.json.return_value ← set this to your dict
```
Each `.` in the chain adds one level of `return_value`. Once you see this pattern, chaining becomes predictable.
---
## What you can test with mocks that you cannot test otherwise
```python
import requests
# Happy path — normal response:
mock_get.return_value.json.return_value = {'temperature': 22}
# Server error — API returns HTTP 500:
mock_get.return_value.raise_for_status.side_effect = requests.HTTPError('500 Server Error')
# Network failure — no connection at all:
mock_get.side_effect = ConnectionError('network unreachable')
# Timeout — API too slow:
mock_get.side_effect = requests.Timeout('read timeout')
```
Testing these scenarios with a real API is practically impossible. With mocks, each is one line.
---
## Part 1 — unittest.mock (standard library)
Despite its name, `unittest.mock` is not tied to the `unittest` test framework. It is Python's **general-purpose mocking module** (standard library since Python 3.3), used equally by pytest, unittest, and any other setup. No installation needed:
```python
from unittest.mock import MagicMock, patch
```
### MagicMock — a recording fake object
`MagicMock` auto-creates any attribute or method you access on it. Every call returns another `MagicMock` unless you specify otherwise — you never get `AttributeError`:
```python
from unittest.mock import MagicMock
mock = MagicMock()
mock.method.return_value = 42
result = mock.method('any', 'args') # → 42
mock.method.assert_called_once_with('any', 'args') # passes
print(mock.method.call_count) # 1
```
### patch — replacing a name in a module's namespace
`patch` temporarily swaps a name in a module's namespace for the duration of a test, then restores the original automatically.
**Decorator form** — the mock is passed as an extra argument:
```python
from unittest.mock import patch
@patch('mymodule.requests.get')
def test_fetch(mock_get):
mock_get.return_value.json.return_value = {'price': 150}
result = mymodule.fetch_price('AAPL')
assert result == 150
```
**Context manager form** — useful when you need to patch only part of a test:
```python
def test_fetch():
with patch('mymodule.requests.get') as mock_get:
mock_get.return_value.json.return_value = {'price': 150}
result = mymodule.fetch_price('AAPL')
assert result == 150
```
### The patch path rule: where it's *used*, not where it's *defined*
This is the most common source of confusion. Patch the name **as your module sees it**:
```python
# If mymodule.py does: import requests
@patch('mymodule.requests.get') # correct
# If mymodule.py does: from requests import get
@patch('mymodule.get') # correct — 'get' is now its own name in mymodule
@patch('requests.get') # wrong — mymodule.get already holds a separate reference
```
**Why this matters:** when Python executes `import requests` in your module, it creates a name `requests` in that module's namespace pointing to the module object. Patching `mymodule.requests.get` replaces the `get` attribute on that namespace. If you patch `requests.get` directly, you modify the original module — but if `mymodule` used `from requests import get`, it already has its own copy of the function reference and your patch never touches it.
Mental model: the patch path is a postal address. `'weather.requests.get'` means *"go to the weather module (the building), find the requests object (the floor), replace get (the door)"*. You must give the address of where the function *lives* in your code, not where it was originally delivered from.
### return_value vs side_effect
```python
mock.return_value = 42 # returns 42 on every call
mock.side_effect = ConnectionError # raises this exception class on call
mock.side_effect = [1, 2, 3] # returns items in sequence, one per call
mock.side_effect = lambda x: x * 2 # calls this function with the same args
```
`side_effect` overrides `return_value` when set.
### Common beginner mistakes
**Mistake 1: patching the wrong location**
```python
# Code does: import requests
@patch('requests.get') # wrong — patches the source, not the usage
@patch('mymodule.requests.get') # correct
```
**Mistake 2: forgetting return_value chaining**
```python
# wrong — sets what mock_get itself returns, not what .json() returns
mock_get.return_value = {'temperature': 22}
# correct — resp.json() should return the dict, not resp itself
mock_get.return_value.json.return_value = {'temperature': 22}
```
**Mistake 3: asserting before calling**
```python
@patch('mymodule.func')
def test_something(mock_func):
mock_func.assert_called() # fails — nothing called it yet
result = mymodule.do_something()
mock_func.assert_called() # passes — call it first, assert after
```
**Mistake 4: not asserting at all**
```python
@patch('mymodule.requests.get')
def test_fetch(mock_get):
mock_get.return_value.json.return_value = {'data': 1}
result = mymodule.fetch()
assert result == 1
# Missing: assert mock_get was called with the right URL
# The function could be hardcoding a wrong URL and the test still passes
```
---
## Part 2 — pytest-mock (pytest-idiomatic wrapper)
`pytest-mock` is a thin plugin that wraps `unittest.mock` in a pytest fixture called `mocker`. Install once:
```bash
pip install pytest-mock
```
The `mocker` fixture is injected like any other pytest fixture — **no decorator needed**:
```python
def test_fetch(mocker):
mock_get = mocker.patch('mymodule.requests.get')
mock_get.return_value.json.return_value = {'price': 150}
result = mymodule.fetch_price('AAPL')
assert result == 150
```
`mocker.patch` returns the same `MagicMock` object, so all assertion methods are identical.
### What mocker provides
```python
mocker.patch('mymodule.func') # replace a name (returns MagicMock)
mocker.patch.object(instance, 'method') # replace a method on an instance
mocker.MagicMock() # create a standalone mock manually
mocker.patch('mymodule.func', return_value=42) # shorthand: set return_value inline
```
All mocks created through `mocker` are automatically reset and stopped after the test ends — you never manage cleanup manually.
### Where mocker clearly wins: multiple patches
With `@patch`, stacking decorators reverses the argument order — a notorious footgun:
```python
@patch('mymodule.requests.get')
@patch('mymodule.time.sleep')
@patch('mymodule.logger.warning')
def test_retry(mock_warn, mock_sleep, mock_get): # reversed! bottom decorator → first arg
...
```
With `mocker`, each patch is a named local variable in natural order:
```python
def test_retry(mocker):
mock_get = mocker.patch('mymodule.requests.get')
mock_sleep = mocker.patch('mymodule.time.sleep')
mock_warn = mocker.patch('mymodule.logger.warning')
# no order confusion, each mock has a meaningful name
```
### mocker in fixtures — a natural fit
Because `mocker` is a fixture, you can pass it to other fixtures directly:
```python
@pytest.fixture
def patched_client(mocker):
mocker.patch('mymodule.requests.get', return_value=mocker.MagicMock(
status_code=200,
json=lambda: {'results': []},
))
return mymodule.ApiClient()
```
With raw `@patch`, doing the same inside a fixture requires an awkward context manager.
---
## Choosing between them
| | `unittest.mock` directly | `pytest-mock` |
|---|---|---|
| **Installation** | built-in — no install | `pip install pytest-mock` |
| **Style** | `@patch` decorator or `with patch()` | `mocker` fixture parameter |
| **Multiple mocks** | stacked decorators, reversed arg order | separate calls, named variables |
| **Cleanup** | by decorator / context manager | automatic after each test |
| **Use in fixtures** | awkward (context manager in fixture) | natural (pass `mocker` as arg) |
| **Underlying objects** | `MagicMock`, `call`, etc. | identical — just wrapped |
Both are common in real pytest projects. Use `unittest.mock` directly when you want zero extra dependencies or are writing library code. Prefer `pytest-mock` in a dedicated pytest project — it fits the fixture model cleanly and eliminates the decorator argument-order trap when patching multiple things.
## The code under test
```python
# price.py
import requests
import time
BASE_URL = 'https://api.market.example.com'
def fetch_price(ticker):
resp = requests.get(f'{BASE_URL}/price/{ticker}')
resp.raise_for_status()
return resp.json()['price']
def fetch_price_with_retry(ticker, retries=3):
for attempt in range(retries):
try:
return fetch_price(ticker)
except ConnectionError:
if attempt < retries - 1:
time.sleep(1)
raise ConnectionError('all retries failed')
```
## 1. Basic mock — the same test, two styles
```python
# Using unittest.mock (@patch decorator)
from unittest.mock import patch
import pytest, price
@patch('price.requests.get')
def test_fetch_price_stdlib(mock_get):
mock_get.return_value.json.return_value = {'price': 150.0}
mock_get.return_value.raise_for_status.return_value = None
result = price.fetch_price('AAPL')
assert result == 150.0
mock_get.assert_called_once_with(f'{price.BASE_URL}/price/AAPL')
# Using pytest-mock (mocker fixture)
def test_fetch_price_mocker(mocker):
mock_get = mocker.patch('price.requests.get')
mock_get.return_value.json.return_value = {'price': 150.0}
mock_get.return_value.raise_for_status.return_value = None
result = price.fetch_price('AAPL')
assert result == 150.0
mock_get.assert_called_once_with(f'{price.BASE_URL}/price/AAPL')
```
The logic is identical. The only differences: decorator vs fixture parameter, and how the mock is obtained. The `MagicMock` object and all its assertion methods are the same.
## 2. side_effect — both styles
```python
# stdlib
@patch('price.requests.get')
def test_fetch_raises_stdlib(mock_get):
mock_get.side_effect = ConnectionError('network down')
with pytest.raises(ConnectionError):
price.fetch_price('AAPL')
# pytest-mock
def test_fetch_raises_mocker(mocker):
mock_get = mocker.patch('price.requests.get')
mock_get.side_effect = ConnectionError('network down')
with pytest.raises(ConnectionError):
price.fetch_price('AAPL')
```
## 3. Multiple patches — where mocker wins
Testing the retry logic requires mocking both `requests.get` and `time.sleep`.
**stdlib — stacked decorators, reversed argument order:**
```python
from unittest.mock import patch, MagicMock
import price
@patch('price.requests.get') # ← applied second (outer)
@patch('price.time.sleep') # ← applied first (inner)
def test_retry_stdlib(mock_sleep, mock_get): # inner decorator → first arg
success = MagicMock()
success.json.return_value = {'price': 99.0}
success.raise_for_status.return_value = None
mock_get.side_effect = [ConnectionError(), ConnectionError(), success]
result = price.fetch_price_with_retry('AAPL')
assert result == 99.0
assert mock_sleep.call_count == 2
```
The reversed argument order (`mock_sleep` before `mock_get` despite `@patch('...get')` being first) is a well-known source of bugs. Easy to mix up silently — both mocks are `MagicMock` objects with no type to catch the swap.
**pytest-mock — named variables, natural order:**
```python
def test_retry_mocker(mocker):
mock_get = mocker.patch('price.requests.get')
mock_sleep = mocker.patch('price.time.sleep')
success = mocker.MagicMock()
success.json.return_value = {'price': 99.0}
success.raise_for_status.return_value = None
mock_get.side_effect = [ConnectionError(), ConnectionError(), success]
result = price.fetch_price_with_retry('AAPL')
assert result == 99.0
assert mock_sleep.call_count == 2
```
Each mock has a meaningful name. Adding a third patch is just one more line — no decorator reordering, no argument shuffling.
## 4. patch.object — both styles
```python
class ApiClient:
def get(self, url):
... # real network call
# stdlib — context manager required for instance-level patch:
def test_client_stdlib():
client = ApiClient()
with patch.object(client, 'get', return_value={'data': 'ok'}) as mock_get:
result = client.get('/resource')
assert result == {'data': 'ok'}
mock_get.assert_called_once_with('/resource')
# pytest-mock — no context manager, cleanup is automatic:
def test_client_mocker(mocker):
client = ApiClient()
mock_get = mocker.patch.object(client, 'get', return_value={'data': 'ok'})
result = client.get('/resource')
assert result == {'data': 'ok'}
mock_get.assert_called_once_with('/resource')
```
## 5. mocker inside a fixture
```python
# conftest.py
import pytest
@pytest.fixture
def mock_price_service(mocker):
mock = mocker.patch('price.requests.get')
mock.return_value.raise_for_status.return_value = None
mock.return_value.json.return_value = {'price': 42.0}
return mock
# test file — the mock is already active when the test runs
def test_uses_mock_fixture(mock_price_service):
import price
result = price.fetch_price('AAPL')
assert result == 42.0
mock_price_service.assert_called_once()
```
Passing `mocker` to a fixture is natural — it's just another fixture dependency. Doing the same with `@patch` requires a context manager inside the fixture, which is less readable.
**Install pytest-mock:**
```bash
pip install pytest-mock
```
---
**Side-by-side cheatsheet:**
| Task | `unittest.mock` | `pytest-mock` |
|------|-----------------|---------------|
| Patch a name | `@patch('mod.func')` decorator | `mocker.patch('mod.func')` |
| Patch in context only | `with patch('mod.func') as m:` | same `mocker.patch()` — auto-cleaned |
| Patch instance method | `patch.object(obj, 'method')` | `mocker.patch.object(obj, 'method')` |
| Create standalone mock | `MagicMock()` | `mocker.MagicMock()` |
| Multiple patches | stacked decorators (reversed args) | separate calls, named variables |
| Use inside a fixture | context manager in fixture body | pass `mocker` as fixture arg |
---
**return_value / side_effect (identical in both):**
```python
mock.return_value = 42
mock.side_effect = ValueError # raises exception class
mock.side_effect = [1, 2, 3] # sequential returns
mock.side_effect = lambda x: x * 2 # callable — called with same args
```
**Assertions (identical in both):**
```python
mock.assert_called()
mock.assert_called_once()
mock.assert_called_with(*args, **kw) # last call
mock.assert_called_once_with(*args, **kw) # exactly one call
mock.assert_not_called()
mock.call_count # int
mock.call_args_list # list of all calls
```
**Patch path rule (same in both):**
Always patch `'your_module.dependency'`, not `'original_module.dependency'`.
For `from x import y` in your module: patch `'your_module.y'`.
**Multiple patches with mocker:**
```python
def test_something(mocker):
mock_a = mocker.patch('mod.func_a') # each has a clear name
mock_b = mocker.patch('mod.func_b') # order doesn't matter
mock_c = mocker.patch('mod.func_c')
```
**Multiple patches with @patch (reversed args):**
```python
@patch('mod.func_a') # outermost → last arg
@patch('mod.func_b')
@patch('mod.func_c') # innermost → first arg
def test_something(mc, mb, ma): # reversed!
...
```