Python unittest.mock: control dependencies and verify behavior

Use Mock, patch, autospec, MagicMock, and AsyncMock to test dependency calls while keeping real interface and integration checks in view.

Best practices
Concurrent tasks moving through a shared processing hub

unittest.mock replaces a dependency with a controllable test double and records how the code used it. It is useful for network clients, clocks, and failure paths that are difficult to reproduce reliably. A mock can make a unit test deterministic, but it cannot prove that the real dependency behaves as configured.

The module is part of Python's standard library. The examples below use modern Python; AsyncMock requires Python 3.8 or later.

Configure a result and verify the call

from unittest.mock import Mock

api = Mock()
api.get_user.return_value = {"name": "Alex"}

result = api.get_user(1)
assert result == {"name": "Alex"}
api.get_user.assert_called_once_with(1)

An unrestricted mock creates many child attributes lazily and records calls through properties such as called, call_count, and call_args. That flexibility can hide a misspelled interface. Some reserved or assertion-like names are protected; a mock does not simply accept every possible attribute.

Use spec to constrain accessible attributes, or spec_set to restrict both getting and setting attributes. Autospeccing additionally models callable signatures. See the mock reference.

Return a value, a sequence, or a failure

Use separate mocks when demonstrating different behaviors:

from unittest.mock import Mock

fixed = Mock(return_value=42)
assert fixed() == 42

sequence = Mock(side_effect=[1, 2, ValueError("Bad result")])
assert sequence() == 1
assert sequence() == 2
try:
    sequence()
except ValueError:
    pass
else:
    raise AssertionError("Expected ValueError")

doubled = Mock(side_effect=lambda value: value * 2)
assert doubled(3) == 6

An iterable side_effect supplies successive outcomes; exception entries are raised, and exhausting the iterable raises StopIteration. An exception class or instance can also be assigned directly. A callable side_effect computes the result from the arguments.

These settings are not all independent replacements of one another. side_effect takes precedence over return_value unless it returns DEFAULT; setting side_effect=None disables it and lets the return-value behavior apply again. Reassigning side_effect replaces its previous configuration.

Patch the name the code actually looks up

Here is a small module with a real HTTP dependency. Install requests, and save it as mymodule.py:

from datetime import datetime, timezone
import requests


def fetch_data():
    response = requests.get("https://example.test/data", timeout=5)
    response.raise_for_status()
    return response.json()


def current_time():
    return datetime.now(timezone.utc)

Save this next to it as test_mymodule.py:

from datetime import datetime, timezone
from unittest import TestCase, main
from unittest.mock import patch
import mymodule

FIXED_TIME = datetime(2026, 1, 1, tzinfo=timezone.utc)


class ModuleTests(TestCase):
    @patch("mymodule.requests.get", autospec=True)
    def test_fetch(self, mock_get):
        mock_get.return_value.json.return_value = {"ok": True}
        self.assertEqual(mymodule.fetch_data(), {"ok": True})
        mock_get.assert_called_once_with("https://example.test/data", timeout=5)
        mock_get.return_value.raise_for_status.assert_called_once_with()

    def test_time(self):
        with patch("mymodule.datetime") as mock_datetime:
            mock_datetime.now.return_value = FIXED_TIME
            self.assertEqual(mymodule.current_time(), FIXED_TIME)
            mock_datetime.now.assert_called_once_with(timezone.utc)


if __name__ == "__main__":
    main()

Run python -m unittest -v test_mymodule. The patched HTTP call makes no network request. The context manager restores the clock reference when its block exits, including on an exception; the decorator limits its patch to the test invocation.

Patch where the dependency is looked up. Here, mymodule imports the requests module and calls requests.get, so mymodule.requests.get is a valid target. If it used from requests import get, the lookup would instead be mymodule.get. A reference imported before a patch is not automatically rebound when the original defining module is patched. See where to patch.

Use MagicMock for protocols and AsyncMock for awaits

MagicMock preconfigures support for many special methods used by operations such as indexing, iteration, and context management:

from unittest.mock import MagicMock

collection = MagicMock()
collection.__len__.return_value = 5
assert len(collection) == 5

patch.object() temporarily replaces an attribute on a specific object. patch.dict() temporarily changes dictionary entries, such as a test's environment configuration, and restores them afterwards.

For an async dependency, distinguish being called from being awaited:

import asyncio
from unittest.mock import AsyncMock


async def main():
    fetch = AsyncMock(return_value={"ok": True})
    result = await fetch("record-1")
    assert result == {"ok": True}
    fetch.assert_awaited_once_with("record-1")


asyncio.run(main())

A plain mock's default result is not awaitable. Modern patch() selects AsyncMock for an async function when creating a replacement automatically, but explicitly supplied replacements remain your responsibility.

Keep tests aligned with the real interface

Autospeccing can catch a call with the wrong signature:

from unittest.mock import create_autospec


def lookup(user_id, *, include_disabled=False):
    return {"id": user_id}


lookup_mock = create_autospec(lookup, return_value={"id": 7})
assert lookup_mock(7) == {"id": 7}
lookup_mock.assert_called_once_with(7)

It does not check the correctness of your chosen return data or simulate a server's semantics. Dynamic attributes and introspection can also complicate autospec; use an appropriate interface or instance as the specification.

Arrange the dependency behavior, call the code under test, then assert both its meaningful output and the interaction that matters. Use mock_calls or call assertions when order is part of the contract, rather than locking every internal implementation detail into a test.

Avoid mocking each layer beneath a dependency. If a unit test merely reasserts a large mock setup, it can stay green while the real integration is broken. Keep focused tests against real boundaries, such as a disposable database, where transactions, queries, or serialization matter.

Finally, call assertion methods. Merely evaluating mock.assert_called_once retrieves a method and performs no assertion; use mock.assert_called_once() or assert_called_once_with(...).

Common questions

Should I use a patch decorator or a with block?

Use a decorator when the entire test needs the replacement, and a context manager when a smaller scope makes the test clearer. Class decorators can apply patches across selected test methods. Keep patch scope as narrow as the scenario permits.

How should I test async dependencies?

Use AsyncMock, await the relevant code, and assert await history where that is the behavior you need to verify. A call assertion alone cannot prove the returned coroutine was awaited.

When should I avoid mocks?

Pure functions and value objects often need only direct inputs and assertions. For complicated database behavior, a real isolated test database may provide better evidence than layers of simulated cursors. Mock external nondeterminism where it helps, then verify the important real integration separately.

Keep test evidence separate from runtime diagnostics

Mocked failures belong in test reports. For failures observed in a deployed Python service, collect the real error category and request context through Guance logging. Do not treat a passing mocked network test as evidence that an external service or production collection path works.

The examples and module tests were run locally without an external HTTP request. No production dependency or Guance ingestion test was performed. Related reading: Python type hints (Chinese) and Rails, Laravel, and Django (Chinese).

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting