Last Updated: June 01, 2026

Intermediate

You have written a function that sends emails, calls a third-party API, or reads from a database. You want to write unit tests for it, but running those tests would actually send emails, hit the live API, and modify real data. Worse, the tests would be slow, flaky (depending on network availability), and expensive (API calls cost money). The solution is mocking: replacing the real dependency with a controlled fake that behaves exactly how you tell it to.

Python’s unittest.mock module is the standard library’s built-in mocking toolkit. It ships with Python 3.3+ at no extra installation cost and integrates seamlessly with both unittest and pytest. The module provides Mock and MagicMock objects that record all calls made to them, plus a patch() decorator and context manager that temporarily swaps real objects with mocks in your code’s namespace.

This article covers the complete mocking workflow: creating basic mocks, configuring return values and side effects, using patch() to intercept dependencies, mocking HTTP requests, mocking time, and asserting on mock call history. By the end, you will be able to test any function in isolation, no matter what external systems it depends on.

Pubs - Python How To Program
Written by Pubs

Python developer and educator with 15+ years building production systems across data engineering, web APIs, and AI tooling. Founder of Python How To Program — 270+ in-depth tutorials covering the modern Python stack.

View all tutorials by Pubs →

unittest.mock Quick Example

Part of the Python Testing & Quality Hub. See the full hub for related Python tutorials.

Here is the pattern in its simplest form: a function that calls an external API, tested without hitting any network.

# quick_mock_example.py
import unittest
from unittest.mock import patch, MagicMock

def get_user_name(user_id: int) -> str:
    """Fetches a user name from an external API (real implementation)."""
    import requests
    response = requests.get(f"https://jsonplaceholder.typicode.com/users/{user_id}")
    response.raise_for_status()
    return response.json()["name"]

class TestGetUserName(unittest.TestCase):
    
    @patch("quick_mock_example.requests.get")
    def test_returns_user_name(self, mock_get):
        # Configure the fake response
        mock_response = MagicMock()
        mock_response.json.return_value = {"id": 1, "name": "Leanne Graham"}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        # Call the function under test
        result = get_user_name(1)
        
        # Assertions
        self.assertEqual(result, "Leanne Graham")
        mock_get.assert_called_once_with(
            "https://jsonplaceholder.typicode.com/users/1"
        )

if __name__ == "__main__":
    unittest.main()

Output:

python -m pytest quick_mock_example.py -v

quick_mock_example.py::TestGetUserName::test_returns_user_name PASSED

1 passed in 0.04s

The test completes in 40 milliseconds with zero network calls. The @patch() decorator intercepts requests.get in the module under test and replaces it with a MagicMock. The mock records the call and returns exactly what we told it to. The deeper sections explain each component in detail.

Mock and MagicMock: The Foundation

A Mock object accepts any attribute access and any method call without raising errors. Every call is recorded. MagicMock is a subclass that additionally supports Python’s magic methods (__len__, __iter__, __enter__, etc.), making it suitable for mocking context managers and containers.

# mock_basics.py
from unittest.mock import Mock, MagicMock

# Any attribute access returns another Mock
m = Mock()
print(m.anything)           # 
print(m.foo.bar.baz())      # 

# Configure return values
m.get_price.return_value = 9.99
print(m.get_price())        # 9.99

# Raise an exception
m.failing_call.side_effect = ValueError("Something went wrong")
try:
    m.failing_call()
except ValueError as e:
    print(e)                # Something went wrong

# Check call history
m.calculate(10, 20)
print(m.calculate.called)               # True
print(m.calculate.call_count)           # 1
print(m.calculate.call_args)            # call(10, 20)

# MagicMock supports __len__, __iter__, context manager
mm = MagicMock()
mm.__len__.return_value = 5
print(len(mm))              # 5

mm.__iter__.return_value = iter([1, 2, 3])
print(list(mm))             # [1, 2, 3]

Output:



9.99
Something went wrong
True
1
call(10, 20)
5
[1, 2, 3]

The key distinction between Mock and MagicMock: use MagicMock by default (it covers everything Mock does plus magic methods), and only drop to Mock if you specifically want attribute access on undefined magic methods to raise AttributeError rather than silently returning another Mock.

Mock replaces real API calls with controlled responses
No network. No database. No problem. The mock says what we need it to say.

The patch() Function: Intercepting Dependencies

patch() is the workhorse of unittest.mock. It temporarily replaces an object in a specific module’s namespace for the duration of a test, then restores it afterward. The target string must be the full dotted path to the object as it is imported in the code under test — not where it is originally defined.

patch() as a Decorator

# patch_decorator.py
import unittest
from unittest.mock import patch, MagicMock

# --- Code under test ---
import os

def get_home_directory() -> str:
    return os.path.expanduser("~")

def read_config_file(path: str) -> dict:
    import json
    with open(path) as f:
        return json.load(f)

# --- Tests ---
class TestWithPatch(unittest.TestCase):
    
    @patch("patch_decorator.os.path.expanduser")
    def test_get_home(self, mock_expand):
        mock_expand.return_value = "/home/testuser"
        result = get_home_directory()
        self.assertEqual(result, "/home/testuser")
        mock_expand.assert_called_once_with("~")
    
    @patch("builtins.open", new_callable=MagicMock)
    @patch("patch_decorator.json.load")
    def test_read_config(self, mock_json_load, mock_open):
        mock_json_load.return_value = {"debug": True, "port": 8080}
        result = read_config_file("/fake/config.json")
        self.assertEqual(result["port"], 8080)
        mock_open.assert_called_once_with("/fake/config.json")

if __name__ == "__main__":
    unittest.main()

Output:

..
----------------------------------------------------------------------
Ran 2 tests in 0.003s
OK

When stacking multiple @patch() decorators, the mocks are passed to the test function in bottom-up order — the bottom-most decorator’s mock is the first argument. This is a common source of confusion. In the example above, mock_json_load comes from the lower @patch("patch_decorator.json.load") and mock_open from the upper @patch("builtins.open").

patch() as a Context Manager

Use the context manager form when you only need the mock for part of a test, or when you are not using a test class.

# patch_context.py
from unittest.mock import patch
import pytest

def send_notification(message: str, email: str) -> bool:
    """Sends an email notification. Returns True on success."""
    import smtplib
    with smtplib.SMTP("smtp.gmail.com", 587) as server:
        server.starttls()
        server.sendmail("noreply@example.com", email, message)
    return True

def test_send_notification_success():
    with patch("patch_context.smtplib.SMTP") as mock_smtp_class:
        mock_server = mock_smtp_class.return_value.__enter__.return_value
        mock_server.sendmail.return_value = {}
        
        result = send_notification("Hello!", "user@example.com")
        
        assert result is True
        mock_server.starttls.assert_called_once()
        mock_server.sendmail.assert_called_once_with(
            "noreply@example.com", "user@example.com", "Hello!"
        )

Output:

pytest patch_context.py -v

patch_context.py::test_send_notification_success PASSED

Mocking a context manager requires one extra step: you need to mock the object returned by __enter__. In the example above, mock_smtp_class.return_value is what smtplib.SMTP("smtp.gmail.com", 587) returns, and .__enter__.return_value is the server variable inside the with block. This chain is the standard pattern for any with statement mock.

Side Effects: Sequences, Exceptions, and Callables

The side_effect attribute gives you fine-grained control over what happens when a mock is called. You can make it raise exceptions, return different values on successive calls, or run a custom function.

# side_effects.py
from unittest.mock import Mock, patch
import unittest

class TestSideEffects(unittest.TestCase):
    
    def test_raise_on_third_call(self):
        """Simulate a flaky API that fails on the 3rd attempt."""
        mock_api = Mock()
        mock_api.side_effect = [
            {"status": "ok", "data": "first"},
            {"status": "ok", "data": "second"},
            ConnectionError("API unreachable"),
        ]
        
        self.assertEqual(mock_api()["data"], "first")
        self.assertEqual(mock_api()["data"], "second")
        with self.assertRaises(ConnectionError):
            mock_api()
    
    def test_dynamic_response(self):
        """Return different responses based on the input."""
        def dynamic(url):
            if "users" in url:
                return {"type": "user", "id": 1}
            return {"type": "unknown"}
        
        mock_get = Mock(side_effect=dynamic)
        self.assertEqual(mock_get("https://api.example.com/users/1")["type"], "user")
        self.assertEqual(mock_get("https://api.example.com/other")["type"], "unknown")

if __name__ == "__main__":
    unittest.main()

Output:

..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK

The list-based side_effect is extremely useful for testing retry logic: set it to a list where the first N elements are exceptions and the last element is the successful response. Your retry function should exhaust the exceptions and succeed on the final call. If the mock runs out of values in the list, it raises StopIteration on the next call.

side_effect list simulates retry logic
side_effect=[error, error, success]. Retry logic tested.

Asserting on Calls

After running the code under test, verify that the mock was called correctly. The assertion methods are specific enough to catch argument order mistakes and missing calls.

# assert_calls.py
from unittest.mock import Mock, call

mock_logger = Mock()

# Simulate some calls
mock_logger.info("Server started on port 8080")
mock_logger.warning("Disk usage at 85%")
mock_logger.info("Request received from 192.168.1.1")

# Basic call assertions
mock_logger.info.assert_called()                   # Was it called at all?
mock_logger.warning.assert_called_once()           # Called exactly once?
mock_logger.error.assert_not_called()              # Was it NOT called?

# Argument assertions
mock_logger.warning.assert_called_with("Disk usage at 85%")

# Assert all calls in order
mock_logger.info.assert_has_calls([
    call("Server started on port 8080"),
    call("Request received from 192.168.1.1"),
])

# Get full call history
print(mock_logger.info.call_args_list)
# [call('Server started on port 8080'), call('Request received from 192.168.1.1')]

print(mock_logger.call_count)   # Total calls across all methods
print(mock_logger.call_args_list)  # All calls in order

Output:

[call('Server started on port 8080'), call('Request received from 192.168.1.1')]
3
[call.info('Server started on port 8080'), call.warning('Disk usage at 85%'), call.info('Request received from 192.168.1.1')]

Use assert_called_once_with() (not assert_called_with()) when you need to verify both the arguments AND that the function was called exactly once. assert_called_with() only checks the most recent call’s arguments — if the mock was called 10 times, it passes as long as the last call matches.

Real-Life Example: Testing a Weather Service Client

This project tests a complete weather service client that fetches data from an external API and caches results — no network required.

# weather_service.py
import requests
from datetime import datetime

class WeatherService:
    """Fetches weather data from an external API."""
    
    BASE_URL = "https://api.open-meteo.com/v1/forecast"
    
    def __init__(self):
        self._cache = {}
    
    def get_temperature(self, latitude: float, longitude: float) -> float:
        """Returns current temperature in Celsius. Raises on HTTP error."""
        cache_key = (round(latitude, 2), round(longitude, 2))
        if cache_key in self._cache:
            return self._cache[cache_key]
        
        params = {
            "latitude": latitude,
            "longitude": longitude,
            "current_weather": True,
        }
        response = requests.get(self.BASE_URL, params=params)
        response.raise_for_status()
        
        data = response.json()
        temp = data["current_weather"]["temperature"]
        self._cache[cache_key] = temp
        return temp
    
    def is_warm(self, latitude: float, longitude: float) -> bool:
        """Returns True if temperature is above 20 C."""
        return self.get_temperature(latitude, longitude) > 20.0

# --- Tests ---
import unittest
from unittest.mock import patch, MagicMock

class TestWeatherService(unittest.TestCase):
    
    def setUp(self):
        self.service = WeatherService()
    
    @patch("weather_service.requests.get")
    def test_get_temperature_success(self, mock_get):
        mock_response = MagicMock()
        mock_response.json.return_value = {
            "current_weather": {"temperature": 23.5, "windspeed": 12.0}
        }
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        temp = self.service.get_temperature(48.85, 2.35)
        self.assertEqual(temp, 23.5)
    
    @patch("weather_service.requests.get")
    def test_caches_result(self, mock_get):
        mock_response = MagicMock()
        mock_response.json.return_value = {"current_weather": {"temperature": 18.0}}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        self.service.get_temperature(51.5, -0.12)
        self.service.get_temperature(51.5, -0.12)  # Second call should use cache
        
        mock_get.assert_called_once()  # API called only once
    
    @patch("weather_service.requests.get")
    def test_raises_on_http_error(self, mock_get):
        mock_response = MagicMock()
        mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404")
        mock_get.return_value = mock_response
        
        with self.assertRaises(requests.exceptions.HTTPError):
            self.service.get_temperature(0, 0)
    
    @patch.object(WeatherService, "get_temperature", return_value=22.0)
    def test_is_warm_true(self, mock_temp):
        self.assertTrue(self.service.is_warm(48.85, 2.35))
    
    @patch.object(WeatherService, "get_temperature", return_value=15.0)
    def test_is_warm_false(self, mock_temp):
        self.assertFalse(self.service.is_warm(48.85, 2.35))

if __name__ == "__main__":
    unittest.main(verbose=2)

Output:

test_caches_result ... ok
test_get_temperature_success ... ok
test_is_warm_false ... ok
test_is_warm_true ... ok
test_raises_on_http_error ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.008s
OK

Note the use of @patch.object() in the last two tests — it patches a method directly on a class instance, which is cleaner than patching through the module path when the class is in the same file. This pattern is especially useful when testing methods that call other methods on the same object, letting you test is_warm() independently of get_temperature().

Mock the dependency. Test the unit. Forever.
Mock the dependency. Test the unit. Forever.

Frequently Asked Questions

Where exactly should I patch — the import source or the import location?

Always patch where the name is used, not where it is defined. If mymodule.py does import requests and uses requests.get(), patch "mymodule.requests.get", not "requests.get". This is the single most common mocking mistake. The rule is: patch the name in the namespace of the code you are testing, because Python looks up names in their own module’s namespace at runtime.

What is autospec and when should I use it?

Pass spec=SomeClass or autospec=True to patch() to create a mock that enforces the real object’s interface. If you call the mock with wrong arguments or access an attribute that does not exist on the real object, the mock raises AttributeError or TypeError immediately. This catches tests that pass because the mock accepts anything, even when the real code would fail. Use autospec=True for production code; skip it for quick exploratory tests.

Should I use unittest.mock or pytest-mock?

Both wrap the same underlying unittest.mock machinery. The pytest-mock package provides a mocker fixture that cleans up automatically and integrates more naturally with pytest‘s fixture system. If your project uses pytest, pytest-mock is slightly more ergonomic: mocker.patch("module.thing") vs the @patch decorator. Either works — the choice is stylistic.

How do I mock datetime.now()?

You cannot patch datetime.datetime.now directly because datetime is a C extension. Instead, either use the freezegun library (@freeze_time("2024-01-01") — covered in the freezegun article) or wrap datetime.now() in your own function and mock that wrapper. For most projects, freezegun is the cleaner solution since it handles all time-related calls in your code automatically.

How do I reset a mock between tests?

Call mock.reset_mock() to clear call history, or simply create a new Mock() in each test’s setUp method. If you are using @patch as a decorator, the mock is automatically fresh for each test because the decorator creates a new mock on each test run. Only manually reset mocks when you are reusing the same mock object across multiple assertions within a single test.

Conclusion

Mocking transforms slow, flaky integration tests into fast, reliable unit tests. You have covered creating Mock and MagicMock objects, configuring return values and side effects, using patch() as both a decorator and context manager, asserting on call history, and applying all of it to a realistic weather service client with five passing tests in 8 milliseconds. The key rule to remember: always patch where the name is used, not where it is defined.

Extend the weather service tests by adding a test for the cache key rounding behaviour (try coordinates that differ only in the third decimal place), and add a test that verifies the HTTP parameters passed to requests.get() using assert_called_once_with(). These edge cases are exactly what mocking was designed to cover cheaply and reliably.

For the complete API reference, see the official unittest.mock documentation.

Why Mock Dependencies?

Tests should be fast, deterministic, and isolated. A test that hits a real database is slow; one that hits an external API is flaky; one that depends on the current time is non-deterministic. Mocks replace those dependencies with predictable stand-ins:

from unittest.mock import Mock

# Create a mock object
api_client = Mock()
api_client.fetch_user.return_value = {"id": 1, "name": "Alice"}

# Use it in your code — looks like the real thing
user = api_client.fetch_user(42)
print(user)   # {'id': 1, 'name': 'Alice'}

# Inspect what happened
print(api_client.fetch_user.called)            # True
print(api_client.fetch_user.call_args)         # call(42)
print(api_client.fetch_user.call_count)        # 1

Mock auto-creates attributes — mock.anything.you.access just works. The mock records every interaction so you can assert what happened.

patch() Decorator and Context Manager

from unittest.mock import patch

# Decorator form
@patch("myapp.module.send_email")
def test_signup(mock_send):
    mock_send.return_value = True
    result = signup_user("alice@example.com")
    mock_send.assert_called_once_with("alice@example.com", "Welcome")
    assert result == "signed up"

# Context manager form
def test_other():
    with patch("myapp.module.send_email") as mock_send:
        mock_send.return_value = True
        signup_user("alice@example.com")

patch() replaces an attribute during the test, restores it after. The argument is the dotted path to where the function is LOOKED UP, not where it’s defined — a common source of confusion.

Where to Patch: The Most Common Gotcha

# myapp/services.py
from myapp.email import send_email

def signup_user(email):
    send_email(email, "Welcome")

# CORRECT — patch where send_email is USED
@patch("myapp.services.send_email")
def test_signup(mock_send):
    signup_user("alice@example.com")
    mock_send.assert_called_once()

# WRONG — this patches the definition, but services.py has its own reference
@patch("myapp.email.send_email")
def test_signup(mock_send):
    signup_user("alice@example.com")
    mock_send.assert_not_called()    # surprise — wasn't called

The rule: patch the module where the function is REFERENCED, not where it’s defined.

Mocking Side Effects

from unittest.mock import Mock, patch

# Return different values for successive calls
mock = Mock()
mock.side_effect = [1, 2, 3]
print(mock())   # 1
print(mock())   # 2
print(mock())   # 3

# Raise an exception
mock = Mock()
mock.side_effect = ConnectionError("network down")
try:
    mock()
except ConnectionError as e:
    print(e)

# Side effect as a function
def custom_logic(value):
    return value * 2 if value > 0 else 0
mock = Mock(side_effect=custom_logic)
print(mock(5))    # 10
print(mock(-3))   # 0

MagicMock vs Mock

from unittest.mock import MagicMock

# MagicMock auto-implements magic methods (__len__, __iter__, __enter__, etc.)
m = MagicMock()
m.__len__.return_value = 3
print(len(m))    # 3

# Useful for mocking context managers
with MagicMock() as m:
    pass    # __enter__ and __exit__ work

# Iteration
m.__iter__.return_value = iter([1, 2, 3])
for x in m:
    print(x)

MagicMock is Mock + sensible defaults for dunder methods. Use it for objects you’ll use with with, for, len(), etc. The plain Mock doesn’t auto-support those.

Assertions on Mock Calls

mock = Mock()
mock(1, 2, x="hi")
mock(3, 4)

# Was it called at all?
mock.assert_called()

# Was it called exactly once?
mock.assert_called_once()

# Was it called with specific arguments?
mock.assert_called_with(3, 4)              # the LAST call
mock.assert_any_call(1, 2, x="hi")          # ANY of the calls

# Was it called exactly once with specific arguments?
mock.assert_called_once_with(3, 4)          # raises — was called twice

# Call counts and arg history
mock.call_count                              # 2
mock.call_args_list                          # [call(1, 2, x='hi'), call(3, 4)]

Common Pitfalls

  • Patching the wrong path. Patch where the function is USED, not where defined. Single most common mock mistake.
  • Forgetting to reset. Mock state persists across tests if you reuse module-level mocks. Use patch() so cleanup is automatic, or call mock.reset_mock().
  • Asserting on a non-call. Calling mock.foo auto-creates a child mock; mock.foo.assert_called() succeeds even if the test code never called foo. Use spec=Class to lock down the interface.
  • Mock not iterable. Plain Mock isn’t. Use MagicMock or set mock.__iter__.return_value = iter(...).
  • Side effect with return_value. If both are set, side_effect wins. Pick one.

FAQ

Q: unittest.mock or pytest-mock?
A: pytest-mock wraps unittest.mock with a fixture (mocker) that integrates cleanly with pytest. Same engine; cleaner ergonomics for pytest users.

Q: Mock vs MagicMock vs AsyncMock?
A: Mock for basic; MagicMock for dunder methods; AsyncMock (3.8+) for async callables that return coroutines.

Q: How do I mock an async function?
A: AsyncMock(return_value=...). The patched function becomes an awaitable returning your value.

Q: spec= argument — why use it?
A: Mock(spec=SomeClass) rejects attribute access not on SomeClass. Prevents typos that pass silently. Always recommended.

Q: How do I verify call order across mocks?
A: from unittest.mock import call; mock_a.assert_has_calls([call(1), call(2)]); for cross-mock order use a manager: mgr = Mock(); mgr.attach_mock(mock_a, 'a').

Wrapping Up

Mocking is essential for fast, deterministic, isolated tests. Master patch() with the right target path, Mock vs MagicMock vs AsyncMock, and the assertion family. Use spec= to lock interfaces and catch typos. For pytest users, pytest-mock‘s mocker fixture is the cleaner ergonomics. With these tools, your test suite stays fast and your mocks don’t lie.