Table of Contents
- What is
unittest.mock? - Key Components of
unittest.mock - Basic Usage: Creating and Configuring Mocks
- Using
patch()to Replace Objects - Common Mocking Scenarios
- Advanced Techniques
- Best Practices for Effective Mocking
- Conclusion
- References
What is unittest.mock?
unittest.mock is a Python standard library module designed to simplify the creation of mock objects for unit testing. It allows you to:
- Replace external dependencies (e.g., APIs, databases) with controlled substitutes.
- Verify that certain functions/methods were called with the expected arguments.
- Simulate return values, exceptions, or complex behaviors (e.g., returning different values on successive calls).
Unlike third-party mocking libraries (e.g., pytest-mock), unittest.mock is built into Python, so no additional installation is required. It integrates seamlessly with Python’s built-in unittest framework and works with pytest as well.
Key Components of unittest.mock
Mock and MagicMock
The Mock class is the heart of unittest.mock. It creates flexible mock objects that can be called like functions, have attributes, and track interactions (e.g., how many times they were called).
MagicMock is a subclass of Mock that includes default implementations for Python’s “magic methods” (e.g., __len__, __getitem__, __iter__). Use MagicMock when you need to mock objects that rely on magic methods (e.g., lists, dictionaries, or custom classes with magic methods).
from unittest.mock import Mock, MagicMock
# Basic Mock
mock = Mock()
mock() # Call the mock
mock.assert_called_once()
# MagicMock (supports magic methods out of the box)
magic_mock = MagicMock()
magic_mock.__len__.return_value = 5
assert len(magic_mock) == 5 # Works because __len__ is mocked
patch()
The patch() function is used to temporarily replace an object in your code with a mock during testing. It’s the most commonly used tool in unittest.mock because it lets you isolate the code under test by swapping out external dependencies.
patch() can be used as a decorator, context manager, or manually (via start()/stop()), and it automatically cleans up after itself to avoid polluting other tests.
PropertyMock
PropertyMock is used to mock properties (attributes defined with the @property decorator) or other descriptors. It allows you to set return values for attribute access or verify that the property was accessed.
AsyncMock
Introduced in Python 3.8, AsyncMock is designed to mock asynchronous functions (coroutines). It mimics the behavior of async def functions, allowing you to await mock calls and verify interactions with async code.
Basic Usage: Creating and Configuring Mocks
Setting Return Values
Mocks can return predefined values when called. Use return_value to set a fixed return value:
from unittest.mock import Mock
def test_mock_return_value():
# Create a mock and set its return value
mock = Mock()
mock.return_value = "Hello, Mock!"
# Call the mock
result = mock()
# Verify the result
assert result == "Hello, Mock!"
# Verify the mock was called once
mock.assert_called_once()
Using side_effect
side_effect is more flexible than return_value. It can:
- Return different values on successive calls (using an iterable).
- Raise exceptions.
- Call a custom function to compute the return value.
Example 1: Return Different Values
mock = Mock()
mock.side_effect = [1, 2, 3] # First call returns 1, second 2, etc.
assert mock() == 1
assert mock() == 2
assert mock() == 3
Example 2: Raise an Exception
mock = Mock()
mock.side_effect = ValueError("Oops!")
try:
mock()
except ValueError as e:
assert str(e) == "Oops!" # Exception is raised
mock.assert_called_once()
Example 3: Custom Function
def add(a, b):
return a + b
mock = Mock()
mock.side_effect = add # Delegate to the add function
assert mock(2, 3) == 5 # mock(2,3) calls add(2,3)
mock.assert_called_once_with(2, 3)
Verifying Calls
Mocks track every call made to them, allowing you to verify:
- If the mock was called (
assert_called()). - How many times it was called (
assert_called_once(),call_count). - What arguments it was called with (
assert_called_with(),call_args).
mock = Mock()
# Call the mock with arguments
mock("foo", bar=42)
# Verify calls
mock.assert_called_once()
mock.assert_called_with("foo", bar=42)
assert mock.call_count == 1
assert mock.call_args == (("foo",), {"bar": 42}) # Tuple of (args, kwargs)
Using patch() to Replace Objects
The “Where to Patch” Rule
A common pitfall is patching an object in the wrong place. The rule is:
Patch the object where it is imported, not where it is defined.
Example: Suppose you have:
my_project/
├── my_module.py
└── test_my_module.py
my_module.py imports requests and uses requests.get:
# my_module.py
import requests
def fetch_data(url):
response = requests.get(url) # Uses requests.get
return response.json()
To test fetch_data, you need to patch requests.get as imported in my_module, not the global requests module. Thus, you patch my_module.requests.get.
Patch as a Decorator, Context Manager, or Manual
1. Patch as a Decorator
# test_my_module.py
from unittest.mock import patch
from my_module import fetch_data
@patch("my_module.requests.get") # Patch my_module's requests.get
def test_fetch_data(mock_get):
# Configure the mock response
mock_response = Mock()
mock_response.json.return_value = {"key": "value"}
mock_get.return_value = mock_response # requests.get returns mock_response
# Call the function under test
result = fetch_data("https://api.example.com")
# Verify interactions
assert result == {"key": "value"}
mock_get.assert_called_once_with("https://api.example.com") # Check URL
mock_response.json.assert_called_once() # Ensure .json() was called
2. Patch as a Context Manager
Use a context manager if you only need the mock for a specific block of code:
def test_fetch_data_context_manager():
with patch("my_module.requests.get") as mock_get:
mock_response = Mock()
mock_response.json.return_value = {"key": "value"}
mock_get.return_value = mock_response
result = fetch_data("https://api.example.com")
assert result == {"key": "value"}
3. Manual Patch (start/stop)
For fine-grained control, start and stop the patch manually:
def test_fetch_data_manual():
patcher = patch("my_module.requests.get")
mock_get = patcher.start() # Start patching
try:
mock_response = Mock()
mock_response.json.return_value = {"key": "value"}
mock_get.return_value = mock_response
result = fetch_data("https://api.example.com")
assert result == {"key": "value"}
finally:
patcher.stop() # Always stop to clean up!
Patching Objects and Multiple Dependencies
Patch an Object’s Method with patch.object
Use patch.object() to patch a specific method of an object:
class MyClass:
def method(self):
return "Real value"
def test_patch_object():
obj = MyClass()
with patch.object(obj, "method", return_value="Mocked value"):
assert obj.method() == "Mocked value" # Method is patched
assert obj.method() == "Real value" # Patch is removed after context
Patch Multiple Dependencies with patch.multiple
Use patch.multiple() to patch several objects at once:
from unittest.mock import patch, Mock
def test_multiple_patches():
with patch.multiple(
"my_module",
func1=Mock(return_value=1),
func2=Mock(return_value=2)
) as mocks:
# mocks is a dict of patched objects: {"func1": mock1, "func2": mock2}
assert my_module.func1() == 1
assert my_module.func2() == 2
mocks["func1"].assert_called_once()
Common Mocking Scenarios
Mocking External APIs
Let’s expand the earlier fetch_data example to test error handling (e.g., HTTP 404):
# test_my_module.py
from unittest.mock import patch, Mock
from my_module import fetch_data
def test_fetch_data_404_error():
with patch("my_module.requests.get") as mock_get:
# Mock a 404 response
mock_response = Mock()
mock_response.status_code = 404
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found")
mock_get.return_value = mock_response
# Test that fetch_data raises an error
try:
fetch_data("https://api.example.com/invalid")
except requests.exceptions.HTTPError as e:
assert "404 Not Found" in str(e)
mock_get.assert_called_once_with("https://api.example.com/invalid")
Mocking Database Interactions
Suppose you have a function that queries a database with sqlite3:
# my_db_module.py
import sqlite3
def get_user(user_id):
conn = sqlite3.connect("mydb.db")
cursor = conn.cursor()
cursor.execute("SELECT name FROM users WHERE id = ?", (user_id,))
user = cursor.fetchone()
conn.close()
return user[0] if user else None
To test get_user without a real database, mock sqlite3.connect, cursor.execute, and cursor.fetchone:
# test_my_db_module.py
from unittest.mock import patch, Mock
from my_db_module import get_user
@patch("my_db_module.sqlite3.connect")
def test_get_user(mock_connect):
# Mock the connection and cursor
mock_conn = Mock()
mock_cursor = Mock()
mock_connect.return_value = mock_conn
mock_conn.cursor.return_value = mock_cursor
# Mock fetchone to return a user
mock_cursor.fetchone.return_value = ("Alice",)
# Call the function under test
result = get_user(1)
# Verify interactions
assert result == "Alice"
mock_connect.assert_called_once_with("mydb.db")
mock_conn.cursor.assert_called_once()
mock_cursor.execute.assert_called_once_with(
"SELECT name FROM users WHERE id = ?", (1,)
)
mock_cursor.fetchone.assert_called_once()
mock_conn.close.assert_called_once()
Mocking File System Operations
To test code that reads/writes files without touching the real file system, mock open() using patch("builtins.open"):
# file_utils.py
def read_file(filename):
with open(filename, "r") as f:
return f.read()
Test it with:
# test_file_utils.py
from unittest.mock import patch, mock_open
from file_utils import read_file
def test_read_file():
# Mock open() and set its read data
mock_file = mock_open(read_data="Hello, World!")
with patch("builtins.open", mock_file):
content = read_file("test.txt")
assert content == "Hello, World!"
mock_file.assert_called_once_with("test.txt", "r")
mock_file().read.assert_called_once() # Check that read() was called
mock_open is a convenience function that mocks the open() context manager.
Advanced Techniques
Mocking Properties with PropertyMock
Use PropertyMock to mock attributes that are properties:
class MyClass:
@property
def value(self):
return 42 # Real property
def test_property_mock():
with patch("__main__.MyClass.value", new_callable=PropertyMock) as mock_prop:
mock_prop.return_value = 99
obj = MyClass()
assert obj.value == 99 # Property returns mock value
mock_prop.assert_called_once() # Verify the property was accessed
Async Mocking with AsyncMock
For async code, use AsyncMock to mock coroutines:
# async_module.py
import aiohttp
async def async_fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
Test it with AsyncMock:
# test_async_module.py
from unittest.mock import patch, AsyncMock
import pytest
from async_module import async_fetch_data
@pytest.mark.asyncio
async def test_async_fetch_data():
with patch("async_module.aiohttp.ClientSession") as mock_session_cls:
# Mock ClientSession and its get method
mock_session = AsyncMock()
mock_session_cls.return_value.__aenter__.return_value = mock_session
# Mock the response
mock_response = AsyncMock()
mock_response.json.return_value = {"data": "mocked"}
mock_session.get.return_value.__aenter__.return_value = mock_response
# Await the async function
result = await async_fetch_data("https://api.example.com")
assert result == {"data": "mocked"}
mock_session.get.assert_awaited_once_with("https://api.example.com")
mock_response.json.assert_awaited_once()
Strict Mocks with spec and spec_set
By default, mocks allow accessing any attribute (e.g., mock.invalid_attr returns another mock). To enforce that mocks only have attributes/methods present on a real object (to catch typos), use spec or spec_set:
spec: Mocks can’t have attributes not in the spec, but existing attributes can be modified.spec_set: Mocks can’t have new attributes or modify existing ones (stricter).
class RealClass:
def method(self):
pass
# Create a mock with spec=RealClass
mock = Mock(spec=RealClass)
mock.method() # OK (method exists in RealClass)
mock.invalid_attr # Raises AttributeError (invalid_attr not in RealClass)
Best Practices for Effective Mocking
-
Keep Mocks Simple: Only mock what you need. Over-mocking makes tests brittle and hard to read.
-
Test Behavior, Not Implementation: Focus on what the code does, not how it does it. Avoid asserting internal calls unless critical (e.g., “did the API get called?” vs. “did helper_function get called?”).
-
Avoid Over-Mocking: Don’t mock core language features (e.g.,
list,dict) or simple helper functions. Mock only external dependencies (APIs, databases) or slow operations. -
Use
specfor Safety: Usespecto ensure mocks match the interface of real objects, catching typos likemock.gett()instead ofmock.get(). -
Clean Up Patches:
patch()automatically cleans up, but if using manualstart()/stop(), always stop patches in afinallyblock to avoid test pollution. -
Document Mock Intentions: In complex tests, add comments explaining why a mock is used (e.g., “Mocking API to avoid network calls”).
Conclusion
unittest.mock is a powerful library that simplifies writing isolated, reliable unit tests by replacing external dependencies with controlled mocks. By mastering its core components—Mock, MagicMock, patch(), and advanced tools like AsyncMock—you can test code that interacts with APIs, databases, or file systems without leaving your test suite.
Remember to follow best practices: keep mocks focused, test behavior over implementation, and use spec to enforce interface correctness. With these skills, you’ll write tests that are fast, deterministic, and easy to maintain.
References
- Python Official Documentation:
unittest.mock - Real Python: An Introduction to Mocking in Python
- Testing Python Applications with
unittest.mock(Book by Brian Okken) - pytest-mock: Thin wrapper around
unittest.mockfor pytest (optional, but useful for pytest users)