Mocking

Mocking replaces real objects — a database, a network call, the system clock — with controllable stand-ins so tests can isolate exactly the code under test.

The unittest.mock module is part of the Python standard library and provides Mock, MagicMock, and the patch() function for replacing real objects with fake ones during a test. Mocking lets a test isolate a single unit of code from its dependencies — a slow database, an unreliable network call, or the current time — by substituting an object that records how it was called and returns whatever value the test configures.

What Is Mocking and Why It's Needed

A unit test should verify one piece of logic in isolation. If that logic calls out to a database, an external API, or the filesystem, the test becomes slow, flaky (network failures, timing), and hard to set up (needing a real database populated with fixture data). Mocking replaces the dependency with a stand-in object that behaves predictably and records what happened, so the test can focus purely on the logic under test.

unittest.mock is standard library (available since Python 3.3, no installation needed), even though the surrounding practice of 'mocking' is a general testing concept used across languages.

Mock: A Configurable Fake Object

A Mock instance auto-creates any attribute or method you access on it, returning another Mock by default. You can configure return values directly, and every call is recorded for later assertions.

Any unconfigured attribute or call simply returns a new Mock, which is convenient but also means typos in attribute names silently succeed rather than raising AttributeError — a key pitfall covered later.

from unittest.mock import Mock

client = Mock()
client.get_user.return_value = {"id": 1, "name": "Ada"}

result = client.get_user(user_id=1)
print(result)                       # {'id': 1, 'name': 'Ada'}
client.get_user.assert_called_once_with(user_id=1)

MagicMock: Mock Plus Magic Methods

Plain Mock does not implement Python's dunder ('magic') methods like __len__, __iter__, or __enter__, because those are looked up on the type, not the instance. MagicMock preconfigures sensible defaults for these, so it can stand in for objects used with len(), iteration, context managers, and operators.

patch() (covered next) uses MagicMock by default, which is why patched objects usually support len(), iteration, and the with statement out of the box.

from unittest.mock import Mock, MagicMock

plain = Mock()
try:
    len(plain)
except TypeError as e:
    print("Mock fails:", e)

magic = MagicMock()
magic.__len__.return_value = 3
print(len(magic))                   # 3

Configuring Return Values and Side Effects

return_value sets a fixed value for every call. side_effect is more powerful: it can be an exception class (raised when called), a callable (invoked with the same arguments, its return value used), or an iterable (each call returns the next item, useful for simulating a sequence of results).

from unittest.mock import Mock

flaky = Mock(side_effect=[ConnectionError("timeout"), "ok"])
try:
    flaky()
except ConnectionError:
    print("first call failed as configured")
print(flaky())                      # "ok" on the second call

validator = Mock(side_effect=lambda x: x > 0)
print(validator(5), validator(-5))  # True False

patch(): Replacing an Object for the Duration of a Test

patch() temporarily replaces an attribute (a function, class, or module-level name) with a Mock, then restores the original automatically when the patch ends. It can be used as a decorator, a context manager, or manually with start()/stop().

As a context manager instead of a decorator:

from unittest.mock import patch
import requests

def get_weather(city):
    response = requests.get(f"https://api.example.com/weather/{city}")
    return response.json()["temp"]

@patch("requests.get")
def test_get_weather(mock_get):
    mock_get.return_value.json.return_value = {"temp": 72}
    assert get_weather("Boston") == 72
    mock_get.assert_called_once_with("https://api.example.com/weather/Boston")
def test_get_weather_ctx():
    with patch("requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"temp": 55}
        assert get_weather("Denver") == 55

Patching Where It's Used, Not Where It's Defined

The most common patch() mistake is patching the wrong path. patch() replaces a name in a namespace, so the string passed to patch() must be the path as imported into the module under test, not the path where the object was originally defined.

# module: weather_service.py
from external_api import fetch_temperature

def get_temp(city):
    return fetch_temperature(city)


# WRONG target: patches external_api's copy, weather_service still has its own reference
@patch("external_api.fetch_temperature")
def test_wrong(mock_fetch): ...

# RIGHT target: patches the name as looked up inside weather_service
@patch("weather_service.fetch_temperature")
def test_right(mock_fetch):
    mock_fetch.return_value = 68
    assert get_temp("Miami") == 68

patch.object and Patching Class Attributes

patch.object() patches an attribute on a specific object or class rather than resolving a dotted string path, which is often clearer when the target is already imported in the test module.

from unittest.mock import patch
import weather_service

def test_with_patch_object():
    with patch.object(weather_service, "fetch_temperature", return_value=80):
        assert weather_service.get_temp("LA") == 80

autospec: Catching Interface Mismatches

By default a Mock accepts any attribute access or call signature, so a typo like mock.get_uesr() or calling a method with the wrong number of arguments does not raise an error the way the real object would. autospec (or spec) constrains the mock to the real object's actual interface, so such mistakes fail loudly.

autospec is slightly slower to set up (it inspects the real object) but catches an entire class of bugs where a mock silently drifts out of sync with the real interface — prefer it for any mock of real project code.

from unittest.mock import create_autospec

class Database:
    def get_user(self, user_id: int) -> dict:
        ...

fake_db = create_autospec(Database, instance=True)
fake_db.get_user(1)                 # OK: matches real signature
try:
    fake_db.get_uesr(1)              # typo
except AttributeError as e:
    print("caught:", e)
try:
    fake_db.get_user()               # missing required argument
except TypeError as e:
    print("caught:", e)
@patch("weather_service.fetch_temperature", autospec=True)
def test_autospec_patch(mock_fetch):
    mock_fetch.return_value = 68
    assert weather_service.get_temp("Miami") == 68

Asserting Calls

Beyond configuring return values, mocks record every call so a test can assert exactly how the code under test used the dependency.

from unittest.mock import Mock, call

logger = Mock()
logger.info("start")
logger.info("done", extra={"n": 3})

logger.info.assert_any_call("start")
logger.info.assert_called_with("done", extra={"n": 3})   # most recent call
assert logger.info.call_count == 2
logger.info.assert_has_calls([call("start"), call("done", extra={"n": 3})])

Dependency Isolation Patterns

Mocking works best when a function's external dependencies are passed in or imported at a clear boundary, making them easy to patch. Designing code with dependency injection (accepting a client or clock as a parameter, with a real default) makes tests simpler than patching deep inside third-party internals.

import time

def is_expired(timestamp: float, *, now=time.time) -> bool:
    return now() - timestamp > 3600

def test_is_expired():
    fake_now = lambda: 10_000.0
    assert is_expired(timestamp=5_000.0, now=fake_now) is True
    assert is_expired(timestamp=9_999.0, now=fake_now) is False

Mocking Pitfalls

Overusing mocks turns a test suite into a set of assertions about implementation details rather than behavior, so tests break on harmless refactors and pass even when real integration is broken. Common pitfalls: patching the wrong path (see above), mocking so much that the test no longer exercises real logic, forgetting that Mock silently accepts any attribute (masking typos without autospec), and asserting on call details so specific that any refactor breaks the test.

A useful rule of thumb: mock at the boundary of your system (network calls, disk I/O, external services, the clock), not the internal functions of the code you're actually testing.

Practical Usage and Best Practices

Prefer patch() as a context manager or decorator over manual start()/stop() so the original is restored automatically even if the test raises. Prefer autospec=True for any mock of real project code. Keep the number of mocked objects per test small — many mocks in one test is a sign the test (or the code) needs restructuring. Combine mocking with pytest fixtures for reuse: a fixture can yield a patched object so multiple tests share the setup.

import pytest
from unittest.mock import patch

@pytest.fixture
def mock_fetch_temp():
    with patch("weather_service.fetch_temperature", autospec=True) as m:
        yield m

Complete Example

# email_service.py
class EmailClient:
    def send(self, to: str, subject: str, body: str) -> bool:
        ...  # real network call

def notify_signup(client: EmailClient, email: str) -> bool:
    return client.send(to=email, subject="Welcome!", body="Thanks for signing up.")


# test_email_service.py
from unittest.mock import create_autospec
from email_service import EmailClient, notify_signup

def test_notify_signup_sends_expected_email():
    fake_client = create_autospec(EmailClient, instance=True)
    fake_client.send.return_value = True

    result = notify_signup(fake_client, "[email protected]")

    assert result is True
    fake_client.send.assert_called_once_with(
        to="[email protected]",
        subject="Welcome!",
        body="Thanks for signing up.",
    )

Mental Model and Quick Reference

Think of a mock as a piece of theater: it plays the role of a real object well enough to satisfy the code under test, records its 'lines' (calls), and lets the test director (you) assert the performance matched the script.

Mock()                    -> configurable fake, no dunder methods
MagicMock()               -> Mock + dunder methods (len, iter, with, etc.)
.return_value              -> fixed value on every call
.side_effect                -> exception / callable / iterable per call
patch("module.name")       -> replace a name for the test's duration
patch.object(obj, "attr")  -> patch an attribute on a known object
autospec=True               -> constrain mock to the real interface
.assert_called_with(...)   -> verify how the mock was used

Key Takeaways

unittest.mock is standard library; Mock and MagicMock are configurable fake objects that record their calls.

MagicMock adds support for dunder methods (len, iteration, context managers) that plain Mock lacks.

patch() must target the name as looked up in the module under test, not where the object was originally defined.

autospec (or spec) constrains a mock to the real object's interface, catching typos and wrong call signatures that a plain Mock would silently accept.

side_effect can raise exceptions, call a function, or return successive values from an iterable, giving more control than a fixed return_value.

Mock at system boundaries (I/O, network, time), not internal logic — over-mocking makes tests brittle and disconnected from real behavior.

@patch("external_api.fetch_temperature") # patches the source module def test_get_temp(mock_fetch): mock_fetch.return_value = 70 assert weather_service.get_temp("NYC") == 70 # fails: weather_service still calls the real function

@patch("weather_service.fetch_temperature") # patches the name where it's used def test_get_temp(mock_fetch): mock_fetch.return_value = 70 assert weather_service.get_temp("NYC") == 70 patch() replaces a name in a namespace; because weather_service imported fetch_temperature into its own namespace, that copy — not the original module's — must be the patch target.

client = Mock() client.get_uesr(1) # typo, but Mock happily auto-creates get_uesr and returns another Mock client = create_autospec(RealClient, instance=True) client.get_user(1) # typo now raises AttributeError immediately

A plain Mock accepts any attribute name, so typos and interface drift pass silently; autospec constrains the mock to the real object's actual methods and signatures.

with patch("module.get_data") as mock_get: mock_get.return_value = [1, 2, 3] result = process(get_data()) # calling the real name, not going through the code under test with patch("module.get_data") as mock_get: mock_get.return_value = [1, 2, 3] result = module.process() # process() internally calls get_data(); the patch takes effect there

The mock only affects lookups of the patched name from inside the module under test; calling the (still-imported, now-patched) name directly in the test can bypass the intended code path if the test isn't calling through the real function.

def test_over_mocked(): calc = Mock() calc.add.return_value = 4 assert calc.add(2, 2) == 4 # tests the mock, not any real code def test_add(): calc = Calculator() assert calc.add(2, 2) == 4 # exercises the real implementation

Mocking the very unit under test proves nothing; mocks belong on the dependencies of the code being tested, not on the code being tested itself.

What is the key difference between Mock and MagicMock?

MagicMock preconfigures Python's dunder (magic) methods such as __len__, __iter__, and __enter__/__exit__, so it can stand in for objects used with len(), iteration, or a with statement; plain Mock does not implement these and raises TypeError if you try.

When patching a function that module B imported from module A, what path should patch() target and why?

It should target 'B.function_name' (the name as looked up inside B), not 'A.function_name', because B holds its own reference to the imported name in its own namespace, and patch() only replaces the name in the namespace it's given.

What does side_effect let you do that return_value cannot?

side_effect can raise an exception when called, delegate to a real callable (using its return value), or return successive values from an iterable on repeated calls — return_value only ever produces one fixed value.

Why can a typo in an attribute or method name go undetected when using a plain Mock, and how does autospec fix that?

A plain Mock auto-creates any attribute you access and returns another Mock, so a misspelled method name doesn't raise an error. autospec inspects the real object's actual interface and raises AttributeError/TypeError for names or call signatures that don't match it.

Why is unittest.mock described as standard library while pytest and hypothesis are described as third-party?

unittest.mock ships with CPython and requires no installation; pytest and hypothesis must be installed separately via pip and are maintained outside the Python core distribution.

What is a practical downside of mocking too many objects inside a single test?

The test verifies implementation details and interactions with mocks rather than real behavior, becomes brittle against harmless refactors, and can pass even when the real integration between components is broken.

How would you assert that a mock was called exactly twice, with specific arguments each time, in order?

Use mock.assert_has_calls([call(args1), call(args2)]) to check both calls occurred in that order, and check mock.call_count == 2 to confirm there were no additional calls.

Write a test using patch() as a context manager that replaces os.path.exists (as used in a module named checker) to always return True, then asserts that checker.file_is_ready('x.txt') returns True.

from unittest.mock import patch import checker def test_file_is_ready_when_exists(): with patch("checker.os.path.exists", return_value=True): assert checker.file_is_ready("x.txt") is True

Because checker imports os (or os.path) and calls it as checker.os.path.exists, that is the path patch() must target so the replacement is visible inside checker's own function.

What happens when this code runs?

It prints 1, then 2, then 3 — each call to m() consumes the next item from the side_effect iterable. The fourth call raises StopIteration because the iterable is exhausted.

Write a function is_business_hours(now=datetime.now) that returns True if the current hour (from the injected now callable) is between 9 and 17, then write a test that mocks now to force a specific time.

from datetime import datetime def is_business_hours(now=datetime.now) -> bool: return 9 <= now().hour < 17 def test_is_business_hours_at_noon(): fake_now = lambda: datetime(2024, 1, 1, 12, 0) assert is_business_hours(now=fake_now) is True def test_is_business_hours_at_midnight(): fake_now = lambda: datetime(2024, 1, 1, 0, 0) assert is_business_hours(now=fake_now) is False

Injecting the time source as a default parameter makes the function trivially testable without patch(): the test just passes a fake callable directly, which is a simpler and equally valid isolation technique.

Does the assertion pass or fail, and why might this test give a false sense of safety?

It passes, because logger.warning was indeed called once with 'disk low' — logger.warn (a different, auto-created attribute) is irrelevant to that specific assertion. This illustrates the pitfall: if the real logger object only has .warning (not .warn), a plain Mock would never catch the typo on the .warn line, since it silently accepts any attribute name. autospec against the real logger would catch that mistake.

from unittest.mock import Mock

m = Mock(side_effect=[1, 2, 3])
print(m())
print(m())
print(m())
print(m())
from unittest.mock import Mock

logger = Mock()
logger.warn("disk low")
logger.warning("disk low")

try:
    logger.warning.assert_called_once_with("disk low")
    print("assertion passed")
except AssertionError:
    print("assertion failed")