Skip to main content
Advertisement

mock — Mock Objects

Mock replaces external dependencies (databases, HTTP, file I/O) with fake objects during tests. unittest.mock is part of the standard library, while pytest-mock provides a more convenient interface.


MagicMock Basics

from unittest.mock import MagicMock, patch


# MagicMock: auto-generates all attributes and methods
mock_db = MagicMock()

# Specify return values
mock_db.find_user.return_value = {"id": 1, "name": "Alice"}
mock_db.count.return_value = 42

# Use it
user = mock_db.find_user(user_id=1)
assert user["name"] == "Alice"
assert mock_db.count() == 42

# Verify calls
mock_db.find_user.assert_called_once()
mock_db.find_user.assert_called_with(user_id=1)
mock_db.count.assert_called_once()

# Call count
assert mock_db.find_user.call_count == 1

# Simulate exceptions
mock_db.delete.side_effect = PermissionError("No delete permission")

import pytest
with pytest.raises(PermissionError, match="No delete permission"):
mock_db.delete(user_id=1)

patch — Replace a Specific Path

from unittest.mock import patch, MagicMock
import pytest


# Code under test
# user_service.py
def get_user_from_db(user_id: int) -> dict:
import database # real DB module (replaced with mock in tests)
return database.query(f"SELECT * FROM users WHERE id={user_id}")


# @patch: replaces mock only during function execution
@patch("database.query")
def test_get_user(mock_query):
mock_query.return_value = {"id": 1, "name": "Alice"}

from user_service import get_user_from_db
user = get_user_from_db(1)

assert user["name"] == "Alice"
mock_query.assert_called_once_with("SELECT * FROM users WHERE id=1")


# Context manager style
def test_get_user_context():
with patch("database.query") as mock_query:
mock_query.return_value = {"id": 2, "name": "Bob"}
# test code...


# Apply multiple mocks at once (args passed in reverse order)
@patch("module.ClassB")
@patch("module.ClassA")
def test_multiple_mocks(mock_a, mock_b):
# mock_a = ClassA, mock_b = ClassB
mock_a.return_value.method.return_value = "from A"
mock_b.return_value.method.return_value = "from B"
assert mock_a.return_value.method() == "from A"

patch.object — Replace a Method on a Specific Object

from unittest.mock import patch, MagicMock
import requests


class WeatherService:
def get_temperature(self, city: str) -> float:
response = requests.get(f"https://api.weather.com/{city}")
return response.json()["temp"]


def test_get_temperature():
service = WeatherService()

with patch.object(service, "get_temperature", return_value=25.5) as mock_method:
temp = service.get_temperature("Seoul")
assert temp == 25.5
mock_method.assert_called_once_with("Seoul")


# Mock all of requests.get
def test_http_request():
mock_response = MagicMock()
mock_response.json.return_value = {"temp": 22.0}
mock_response.status_code = 200

with patch("requests.get", return_value=mock_response):
service = WeatherService()
temp = service.get_temperature("Busan")
assert temp == 22.0

pytest-mock (mocker fixture)

# pip install pytest-mock

import pytest


class EmailService:
def send(self, to: str, subject: str, body: str) -> bool:
# In production, sends via SMTP server
import smtplib
...
return True


class UserService:
def __init__(self, email_service: EmailService):
self.email_service = email_service

def register(self, email: str, name: str) -> dict:
user = {"id": 1, "email": email, "name": name}
self.email_service.send(
to=email,
subject="Welcome",
body=f"Hello {name}",
)
return user


# mocker fixture (pytest-mock)
def test_register_sends_email(mocker):
mock_email = mocker.MagicMock(spec=EmailService)
mock_email.send.return_value = True

service = UserService(email_service=mock_email)
user = service.register("alice@example.com", "Alice")

assert user["name"] == "Alice"
mock_email.send.assert_called_once_with(
to="alice@example.com",
subject="Welcome",
body="Hello Alice",
)


# mocker.patch: path-based patching
def test_with_mocker_patch(mocker):
mocker.patch("smtplib.SMTP")

email = EmailService()
result = email.send("test@test.com", "Test", "Body")
# smtplib.SMTP replaced with mock


# spy: real function call + call recording
def test_spy(mocker):
service = UserService(email_service=EmailService())
spy = mocker.spy(service, "register")

service.register("test@test.com", "Test")

spy.assert_called_once()
assert spy.call_count == 1

Using side_effect

from unittest.mock import MagicMock
import pytest


# Return different values in sequence
mock = MagicMock()
mock.side_effect = [1, 2, 3, StopIteration]

assert mock() == 1
assert mock() == 2
assert mock() == 3
with pytest.raises(StopIteration):
mock()


# Implement behavior with a function
def smart_side_effect(url: str) -> dict:
if "error" in url:
raise ConnectionError("Connection failed")
return {"url": url, "status": 200}


mock_get = MagicMock(side_effect=smart_side_effect)

result = mock_get("https://example.com/api")
assert result["status"] == 200

with pytest.raises(ConnectionError):
mock_get("https://example.com/error")


# Test retry logic
def fetch_with_retry(url: str, retries: int = 3) -> dict:
import requests
for attempt in range(retries):
try:
return requests.get(url).json()
except ConnectionError:
if attempt == retries - 1:
raise
raise RuntimeError("Unreachable")


def test_retry_logic(mocker):
mock_get = mocker.patch("requests.get")
mock_response = MagicMock()
mock_response.json.return_value = {"data": "ok"}

# Fail twice, succeed on third attempt
mock_get.side_effect = [
ConnectionError(),
ConnectionError(),
mock_response,
]

result = fetch_with_retry("https://example.com")
assert result["data"] == "ok"
assert mock_get.call_count == 3

Mocking Time and Environment

from unittest.mock import patch
import datetime
import os


def get_current_year() -> int:
return datetime.datetime.now().year


def test_mock_datetime(mocker):
mock_now = mocker.patch("datetime.datetime")
mock_now.now.return_value = datetime.datetime(2024, 6, 15, 12, 0, 0)

assert get_current_year() == 2024


# Environment variable mock
def get_config() -> dict:
return {
"api_key": os.environ.get("API_KEY", ""),
"debug": os.environ.get("DEBUG", "false") == "true",
}


def test_env_mock(monkeypatch):
monkeypatch.setenv("API_KEY", "test-key-123")
monkeypatch.setenv("DEBUG", "true")

config = get_config()
assert config["api_key"] == "test-key-123"
assert config["debug"] is True


# File system mock
def read_config_file(path: str) -> str:
with open(path) as f:
return f.read()


def test_file_mock(mocker):
mock_open = mocker.mock_open(read_data="key=value\ndebug=true")
mocker.patch("builtins.open", mock_open)

content = read_config_file("/etc/config.ini")
assert "key=value" in content

Summary

ToolPurpose
MagicMock()Mock object with auto-generated attributes/methods
return_valueSpecify return value
side_effectRaise exception or return dynamically
@patch("path")Replace a global object/function
patch.object(obj, "method")Replace a method on a specific object
mocker.patchpytest-mock style patching
mocker.spyReal execution + call recording
assert_called_once_with(...)Verify call arguments
monkeypatchTemporarily replace env vars and attributes

Mock objects isolate external dependencies to make tests fast and predictable. However, excessive mocking can drift from real behavior — use them judiciously.

Advertisement