Python Pytest Mock: Functions, Classes, Return Values, Side Effects
python pytest mock functions classes return values and side effects: Learn how to mock functions and classes in Python pytest, control return values, simulate side eff...
python pytest mock functions classes return values and side effects requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you write pytest tests that depend on external services, databases, or complex objects, you often need to replace those dependencies with mocks. Mocking lets you control what a function returns, what exceptions it raises, and how many times it is called. This is essential for keeping tests fast, deterministic, and isolated from external state.
The core tools come from the unittest.mock module, which pytest integrates cleanly. You can patch functions, classes, and attributes, and then set return_value or side_effect to define behavior. This article covers python pytest mock functions classes return values and side effects in practical detail.
Why Mocking Is Central to pytest Tests
Mocking is a testing technique that replaces real objects with controllable stand-ins. In pytest, you use mocks to isolate the unit under test from its dependencies. This means you can test a function that calls an external API without making a network request, or test a database-backed method without a live database.
Mocks also help you verify interactions. You can assert that a function was called with specific arguments, that it was called a certain number of times, or that it raised an expected exception. This level of control is what makes unit tests reliable and fast.
Mocking a Function with return_value
The simplest case is replacing a function with a mock that returns a fixed value. Use unittest.mock.patch as a context manager or decorator.
from unittest.mock import patch from myapp import get_user def fetch_user_name(): user = get_user(1) return user["name"]
To test fetch_user_name without hitting a database:
def test_fetch_user_name(): with patch("myapp.get_user") as mock_get_user: mock_get_user.return_value = {"name": "Alice"} assert fetch_user_name() == "Alice" mock_get_user.assert_called_once_with(1)
The return_value attribute controls what the mock returns when called. You can also set it on a mock instance directly.
If you need the mock to return different values on successive calls, use side_effect with a list.
Controlling Behavior with side_effect
side_effect is more flexible than return_value. It can be:
- A function that computes the return value based on arguments.
- An exception class or instance to raise.
- A list of values to return in sequence.
Raising Exceptions
def test_get_user_raises(): with patch("myapp.get_user") as mock_get_user: mock_get_user.side_effect = KeyError("missing") with pytest.raises(KeyError): fetch_user_name()
Returning a Sequence
def test_sequence(): with patch("myapp.get_user") as mock_get_user: mock_get_user.side_effect = [{"name": "Alice"}, {"name": "Bob"}] assert fetch_user_name() == "Alice" assert fetch_user_name() == "Bob"
When side_effect is a list, each call pops the next value. When the list is exhausted, a StopIteration is raised.
Using a Callable
def dynamic_return(user_id): return {"name": f"User{user_id}"} mock_get_user.side_effect = dynamic_return
This lets you compute the return value from the call arguments.
Mocking Classes and Their Instances
Mocking a class is different from mocking a function. When you patch a class, you replace the class object itself. The mock class returns a new mock instance when called.
from myapp import Database def get_data(): db = Database() return db.query("SELECT * FROM users")
To mock Database:
def test_get_data(): with patch("myapp.Database") as MockDatabase: instance = MockDatabase.return_value instance.query.return_value = [{"id": 1}] assert get_data() == [{"id": 1}]
Here MockDatabase.return_value is the mock instance that Database() returns. You set its methods' return values or side effects as needed.
You can also patch instance methods directly if you already have an object.
Choosing Between Mock and MagicMock
Mock and MagicMock are both from unittest.mock. The difference is that MagicMock pre-configures magic methods like __len__, __iter__, and __getitem__ to work by default. This is useful when your code uses these protocols.
| Feature | Mock | MagicMock |
|---|---|---|
| Magic methods | Not pre-configured | Pre-configured |
| Common use | Simple functions and classes | Objects with magic methods |
| Performance | Slightly lighter | Slightly heavier |
For most mocking in pytest, MagicMock is the safer default because it behaves more like a real object. However, if you only need to stub a function call, Mock is sufficient.
Common Pitfalls When Patching
Patching the Wrong Location
You must patch where the name is looked up, not where it is defined. If myapp imports get_user from another module, patch "myapp.get_user", not "othermodule.get_user".
Forgetting to Set return_value
If you don't set return_value, the mock returns another mock. That can cause confusing behavior when your code expects a real value.
Overusing Mocks
Mocking too much can hide integration issues. Use mocks for external boundaries, but let internal logic run naturally.
Not Asserting Calls
A mock without assertions only verifies that the code ran. Use assert_called_once, assert_called_with, and call_count to verify interactions.
Using pytest-mock for Cleaner Fixtures
The pytest-mock plugin provides a mocker fixture that wraps unittest.mock. It automatically undoes patches after each test, reducing boilerplate.
def test_get_user(mocker): mock_get_user = mocker.patch("myapp.get_user") mock_get_user.return_value = {"name": "Alice"} assert fetch_user_name() == "Alice" mock_get_user.assert_called_once_with(1)
The mocker fixture also gives you mocker.spy, mocker.stub, and other utilities. It is the recommended way to mock in pytest because it integrates with the fixture lifecycle.
Testing Side Effects and Verifying Calls
Mocking is not just about return values. You often need to verify that a function was called with specific arguments, or that it was called a certain number of times. Use the assertion methods on the mock.
mock_get_user.assert_called_once() mock_get_user.assert_called_with(1) mock_get_user.assert_any_call(2) mock_get_user.call_count == 3
You can also inspect call_args_list to see the full history of calls.
For side effects that involve external calls, you can use assert_has_calls with a list of call objects.
Mocking Asynchronous Functions
If your code uses async functions, you need AsyncMock. It is available in Python 3.8+.
from unittest.mock import AsyncMock async def fetch_data(): return await api_call() def test_fetch_data(mocker): mock_api = mocker.patch("myapp.api_call", new=AsyncMock()) mock_api.return_value = {"data": 42} assert await fetch_data() == {"data": 42}
AsyncMock supports await and has the same assertion methods as regular mocks.