Skip to content
  • Mocking & Test Doubles · Python

Mocking & Test Doubles

LearnViaNotes @learnvianotes· 3 min read

A test double stands in for a slow, flaky or external dependency — an API, the clock, the database — so a unit test checks only your code. unittest.mock is the standard toolkit.

Kinds of test doubles

DoubleWhat it doesExample
StubReturns canned answersget_rate() always returns 83.0
MockRecords calls so you can assert on them“send_email was called once with this address”
FakeA working lightweight implementationAn in-memory dict instead of a real database
SpyWraps the real object and records callsMock(wraps=real_func)

Mock basics

Python
from unittest.mock import Mock

api = Mock()
api.get_user.return_value = {"id": 7, "name": "Asha"}   # stub a method

print(api.get_user(7)["name"])                    # Asha
print(api.get_user.call_count)                    # 1
print(api.get_user.call_args)                     # call(7)
api.get_user.assert_called_once_with(7)           # passes silently

print(type(api.anything.you.like).__name__)       # Mock   ← attributes are created on demand

side_effect: exceptions and sequences

Python
from unittest.mock import Mock

def fetch_with_retry(client, attempts=3):
    for i in range(attempts):
        try:
            return client.fetch()
        except ConnectionError:
            if i == attempts - 1:
                raise

client = Mock()
client.fetch.side_effect = [ConnectionError(), ConnectionError(), "data"]   # fail, fail, succeed
print(fetch_with_retry(client), client.fetch.call_count)   # data 3

client.fetch.side_effect = ConnectionError("down")          # always fail
try:
    fetch_with_retry(client)
except ConnectionError as e:
    print("gave up:", e)                                     # gave up: down

double = Mock(side_effect=lambda x: x * 2)                   # compute the return value
print(double(21))                                            # 42

patch: replace a name during a test

patch swaps an attribute for a mock and restores it afterwards. Patch where the name is looked up, not where it was defined: if app.py does from time import time, patch app.time, not time.time.

Python
import time
from unittest.mock import patch

class Greeter:
    def greeting(self):
        hour = time.localtime().tm_hour
        return "Good morning" if hour < 12 else "Good afternoon"

fake = time.struct_time((2026, 9, 29, 9, 0, 0, 1, 272, 0))
with patch.object(time, "localtime", return_value=fake):      # context-manager form
    print(Greeter().greeting())                                # Good morning

@patch.object(Greeter, "greeting", return_value="Hi")          # decorator form
def check(mock_greeting):
    result = Greeter().greeting()
    mock_greeting.assert_called_once()
    return result

print(check())                                                 # Hi
print(time.localtime is not None)                              # True   ← original restored
Python
import os
from unittest.mock import patch, mock_open

with patch.dict(os.environ, {"FEATURE_X": "on"}):     # temporary environment
    print(os.environ["FEATURE_X"])                    # on
print("FEATURE_X" in os.environ)                      # False

with patch("builtins.open", mock_open(read_data="line1\nline2")):
    with open("any/path.txt") as f:                   # no real file touched
        print(f.read().splitlines())                  # ['line1', 'line2']

spec & autospec: mocks that fail like the real thing

Python
from unittest.mock import create_autospec

class PaymentGateway:
    def charge(self, amount: int, currency: str) -> str:
        raise NotImplementedError

gateway = create_autospec(PaymentGateway, instance=True)
gateway.charge.return_value = "txn-1"
print(gateway.charge(500, "INR"))                     # txn-1

try:
    gateway.charge(500)                               # wrong signature → error, like the real method
except TypeError:
    print("signature checked")                        # signature checked
try:
    gateway.refund(1)                                 # no such method on the real class
except AttributeError:
    print("unknown method rejected")                  # unknown method rejected

pytest's monkeypatch

Python
def test_uses_api_key(monkeypatch):
    monkeypatch.setenv("API_KEY", "test-key")
    monkeypatch.setattr("shop.payments.charge", lambda amount: "ok")
    assert checkout(100) == "ok"          # undone automatically after the test

Why do you patch where an object is used, not where it's defined?

Mock vs MagicMock?

What is side_effect used for?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report