Mock Objects and External Dependencies in Python

Learn how to use unittest.mock to replace external dependencies like databases and APIs with mock objects, so your unit tests stay fast and reliable.

8 min read

Unit tests should not depend on databases, web APIs, file systems, or network calls. Those dependencies make tests slow, brittle, and hard to run in any environment.

When your function calls an external service, you replace that service with a mock object. A mock pretends to be the real thing, returns the values you tell it to, and records how your code called it so you can verify the interaction.

Python's unittest.mock module provides the Mock class, the patch function, and several helpers that make mocking straightforward. This article builds on the testing skills from Python unittest explained and shows you how to isolate your code from everything it depends on.

Your first mock object

A Mock object is a blank slate that accepts any attribute access and any call. You configure what it should return, then pass it to your code in place of the real dependency.

pythonpython
from unittest.mock import Mock
 
def send_notification(service, user, message):
    response = service.send(user, message)
    return response.status == "ok"
 
def test_send_notification_success():
    mock_service = Mock()
    mock_service.send.return_value.status = "ok"
 
    result = send_notification(mock_service, "alice", "Hello")
 
    assert result is True
    mock_service.send.assert_called_once_with("alice", "Hello")

The mock service does not actually send anything. When the code calls service.send(), the mock returns an object whose status attribute is "ok".

The test then verifies that the function called the service with the right arguments. This test runs in milliseconds with no network, no API keys, and no external service.

Configuring return values

Every Mock has a return_value attribute that controls what the mock returns when called. The default return value is a new Mock, which lets you chain attribute access without extra configuration.

pythonpython
mock = Mock()
mock.calculate.return_value = 42
 
result = mock.calculate(10, 20)
assert result == 42

For nested objects, set return values at each level. This is common when mocking database clients or API wrappers that return objects with methods:

pythonpython
mock_db = Mock()
mock_db.connect.return_value.execute.return_value.fetchall.return_value = [
    {"id": 1, "name": "Alice"}
]
 
rows = mock_db.connect().execute("SELECT * FROM users").fetchall()
assert rows[0]["name"] == "Alice"

The mock records the full chain of calls. You can assert on the entire interaction to verify your code used the dependency correctly.

Simulating exceptions with side_effect

Real services fail. A database connection times out, an API returns a 500 error, a file permission is denied. Use the side_effect attribute to make your mock raise an exception, simulating failures so you can test your error handling code.

pythonpython
import pytest
 
def test_service_unavailable():
    mock_service = Mock()
    mock_service.send.side_effect = ConnectionError("Service unavailable")
 
    with pytest.raises(ConnectionError):
        send_notification(mock_service, "alice", "Hello")

side_effect can also be a function that produces dynamic return values based on the arguments received. The function runs each time the mock is called, using the actual arguments passed in to decide what to return:

pythonpython
mock = Mock()
mock.lookup.side_effect = lambda key: {"a": 1, "b": 2}.get(key, 0)
 
assert mock.lookup("a") == 1
assert mock.lookup("b") == 2
assert mock.lookup("z") == 0

Set side_effect to an iterable to return a different value on each call. The mock returns the next item from the iterable on each invocation:

pythonpython
mock = Mock()
mock.fetch.side_effect = ["first", "second", "third"]
 
assert mock.fetch() == "first"
assert mock.fetch() == "second"
assert mock.fetch() == "third"

This is useful for testing retry logic, pagination, or any code that calls the same dependency multiple times expecting different results.

Using patch to replace dependencies

Passing mocks as arguments works when your code accepts dependencies explicitly. But much real-world code imports modules directly. The patch function temporarily replaces an object in a module so your test controls what the code sees.

pythonpython
from unittest.mock import patch
import my_module
 
@patch("my_module.send_email")
def test_user_registration(mock_send):
    my_module.register_user("alice@example.com")
    mock_send.assert_called_once()

The decorator replaces my_module.send_email with a Mock for the duration of the test and restores it afterward. The mock is passed as an argument to the test function.

patch also works as a context manager, which is clearer when you only need the mock in part of a test:

pythonpython
def test_with_context_manager():
    with patch("my_module.get_config") as mock_config:
        mock_config.return_value = {"debug": True}
        result = my_module.is_debug_mode()
        assert result is True

The most common patch mistake is patching the wrong location. Always patch the name in the module where it is looked up. If your module has from os import getenv, you must patch my_module.getenv, not os.getenv.

Verifying mock calls

Mocks record every call, and several assertion methods let you verify the interaction. assert_called_once_with checks that the mock was called exactly once with specific arguments:

pythonpython
mock.process.assert_called_once_with("data", timeout=30)

assert_called verifies the mock was called at least once. assert_not_called verifies it was never called, which is useful for testing that error paths skip certain operations. For example, you can confirm that a cleanup function was not invoked when the operation succeeded:

pythonpython
mock.cleanup.assert_not_called()

assert_any_call passes if the mock was ever called with the given arguments, regardless of other calls that happened. This is useful when the same mock is called multiple times and you only care about one specific invocation:

pythonpython
mock.log.assert_any_call("User created", level="info")

For complex interactions, inspect the call_args_list attribute, which contains every call in order. This lets you verify the exact sequence of arguments passed to the mock across multiple invocations:

pythonpython
assert len(mock.update.call_args_list) == 3
assert mock.update.call_args_list[0].args == (1,)

These assertions verify not just what your code returned but how it interacted with its dependencies. That level of verification distinguishes unit testing from just running the code and checking output.

When not to mock

Mocking is powerful, but over-mocking makes tests brittle. Do not mock values like strings or numbers. Do not mock the code you are testing.

Do not mock standard library functions unless they have side effects. A good rule: mock at the boundary of your system.

If it reads a file, mock the open call. If it queries a database, mock the database client. Mock the thing you do not control, not the thing you are testing.

The next article covers measuring test coverage in Python, which shows you how to find code that your tests are not exercising.

Rune AI

Rune AI

Key Insights

  • Mock objects replace real dependencies and record every call so you can assert how your code used them.
  • Use patch as a decorator or context manager to temporarily replace an object during a test.
  • side_effect lets you simulate exceptions, dynamic return values, or a sequence of responses.
  • return_value sets what the mock returns when called; the default is a new Mock instance.
  • Always patch the name where the object is looked up in the module under test, not where it is defined.
RunePowered by Rune AI

Frequently Asked Questions

When should I use a mock instead of the real object?

Use a mock when the real object is slow (database queries), unpredictable (random values, current time), unavailable (external API), or has side effects you want to avoid (sending emails, writing files). Also use mocks when you need to verify that your code called a dependency with specific arguments, which is hard to check with real objects.

What is the difference between Mock and MagicMock?

Mock is the basic class that records calls and lets you set return values. MagicMock is a subclass of Mock that comes with default implementations of Python's magic methods like __len__, __iter__, and __str__ already set up. Use MagicMock when your code interacts with the mock using operators, iteration, or context managers. Use Mock when you only need call recording and attribute access.

How does patch know where to apply the mock?

patch replaces the name in the module where it is looked up, not where it is defined. If module A defines a class and module B imports it with 'from A import MyClass', you must patch 'B.MyClass', not 'A.MyClass'. This is the most common mistake when using patch. Always patch where the object is used, not where it is defined.

Conclusion

Mock objects let you test your code in isolation by replacing slow, unpredictable, or unavailable dependencies. The Mock class records calls, patch swaps objects temporarily, and side_effect simulates behavior. Together they turn integration problems into unit tests.