ScreenshotNeo

BlogHow-to

How to Mock Objects in Python unittest

Learn to replace dependencies with unittest.mock, configure return values and side effects, patch the right namespace, and assert calls without brittle tests.

By the ScreenshotNeo team4 October 202610 min read

Use unittest.mock.patch to temporarily replace a dependency where the code under test looks it up. Configure the replacement with return_value for a fixed result or side_effect for errors, sequences, or argument-dependent behavior. Use autospec=True when you want the mock to follow the real object’s visible API and function signatures.

The most common mistake is patching the dependency where it was originally defined instead of where the tested module imported it. The examples below show the correct target and runnable ways to configure and assert mocks. See the official unittest.mock reference for the full API.

1. A minimal working example

Suppose service.py imports a function directly from gateway.py:

# gateway.py
def fetch_record(record_id):
    # Real implementation might call a remote service.
    raise NotImplementedError
# service.py
from gateway import fetch_record

def label_for(record_id):
    record = fetch_record(record_id)
    return record["label"].upper()

Test label_for without invoking the real gateway:

# test_service.py
from unittest import TestCase
from unittest.mock import patch

from service import label_for

class LabelTests(TestCase):
    @patch("service.fetch_record", autospec=True)
    def test_label_for_uppercases_label(self, fetch_record):
        fetch_record.return_value = {"label": "sample"}

        result = label_for("r-17")

        self.assertEqual(result, "SAMPLE")
        fetch_record.assert_called_once_with("r-17")

Run it with python -m unittest -v from the directory containing these files. The patch target is service.fetch_record because that is the name label_for resolves when it runs. The decorator creates the mock for the test and restores the original name after the test, including when the test raises an exception.

2. Choose a mock and patch surface

Need Use Notes
Replace a callable or attribute temporarily patch("module.name") Use the dotted name looked up by the code under test.
Replace an attribute on an object you already have patch.object(obj, "name") Scopes the replacement to a context manager or decorator.
Replace mapping entries temporarily patch.dict(mapping, values) Useful for environment-like dictionaries and configuration mappings.
Need common protocol methods MagicMock Common magic methods, such as iteration and indexing, are available.
Need API and signature constraints autospec=True or create_autospec Catches many invalid attributes and incorrect function calls.

A plain Mock is suitable for a dependency that is called or whose attributes you configure explicitly. MagicMock is useful when production code treats the replacement like a container or iterable, for example by calling len(value), indexing it, or iterating over it. Avoid choosing MagicMock by default if a simpler mock expresses the dependency more clearly.

3. Patch the name the code looks up

With from gateway import fetch_record, importing service binds a name called fetch_record inside the service module. Patch that name:

@patch("service.fetch_record")
def test_something(fetch_record):
    ...

If instead service.py says import gateway and calls gateway.fetch_record(...), patch service.gateway.fetch_record:

# service.py
import gateway

def label_for(record_id):
    record = gateway.fetch_record(record_id)
    return record["label"].upper()
# In the test
@patch("service.gateway.fetch_record", autospec=True)
def test_label_for(fetch_record):
    fetch_record.return_value = {"label": "sample"}
    ...

Think in terms of runtime lookup: follow the expression used by the function under test, from its module globals or object attributes, and patch the name at that location. This rule also applies when dependencies are imported into packages or stored on objects during initialization.

4. Configure return values and side effects

Fixed return value

Set return_value when every call should return the same known value:

from unittest.mock import Mock

fetch_record = Mock(return_value={"label": "sample"})
record = fetch_record("r-17")
assert record == {"label": "sample"}
fetch_record.assert_called_once_with("r-17")

Raise an exception

Give side_effect an exception class or instance to test error handling:

from unittest.mock import patch

@patch("service.fetch_record", autospec=True)
def test_missing_record_is_handled(fetch_record):
    fetch_record.side_effect = LookupError("record missing")
    # Call the function that handles LookupError and assert its contract.

Return successive values

An iterable side effect supplies one result per call. If the code makes more calls than the iterable contains, the next call raises StopIteration:

from unittest.mock import Mock

read = Mock(side_effect=["first", "second"])
assert read() == "first"
assert read() == "second"

Calculate a result from arguments

Use a function when the response depends on the input:

from unittest.mock import Mock

def response_for(record_id):
    return {"label": record_id.upper()}

fetch_record = Mock(side_effect=response_for)
assert fetch_record("r-17") == {"label": "R-17"}

You can also use side_effect for a sequence containing both values and exceptions. Choose the smallest behavior that represents the case under test; complicated mock scripts can become harder to understand than the production behavior.

5. Assert calls when interactions are part of the contract

Mocks record calls, so you can verify that a dependency received the intended arguments. Common assertions include:

dependency.assert_called()
dependency.assert_not_called()
dependency.assert_called_once()
dependency.assert_called_with("r-17")
dependency.assert_called_once_with("r-17")

When order or multiple calls are meaningful, inspect call_args_list or compare with unittest.mock.call entries:

from unittest.mock import call, Mock

send = Mock()
send("start")
send("finish", status=200)

assert send.call_args_list == [
    call("start"),
    call("finish", status=200),
]

Assert interactions when they express a requirement, such as passing the requested identifier or avoiding a duplicate request. If only the returned behavior matters, prefer asserting the result. Tests that assert incidental call order or internal helper details can break during harmless refactors.

6. Use autospec and spec_set for stricter mocks

A permissive mock creates attributes as they are accessed and accepts many call shapes. That flexibility can hide misspellings and API drift. With autospec=True, patch uses the target as a specification: visible attributes are constrained and mocked functions check their signatures.

@patch("service.fetch_record", autospec=True)
def test_fetch(fetch_record):
    fetch_record.return_value = {"label": "sample"}
    # A call with an invalid signature raises TypeError.

For a mock created directly, use create_autospec. Add spec_set=True when setting attributes absent from the specification should also fail:

from unittest.mock import create_autospec

def fetch_record(record_id, *, include_history=False):
    ...

mock_fetch = create_autospec(fetch_record, spec_set=True)
mock_fetch.return_value = {"label": "sample"}
mock_fetch("r-17", include_history=True)

Autospec uses introspection. It may not see instance attributes created dynamically in __init__, and introspecting properties or descriptors can have side effects. If the real object is dynamic or unsafe to inspect, use a narrower spec, a small fake, or an explicitly configured mock. Consult the official autospeccing notes for limitations.

7. Use context managers, decorators, and cleanup

A context manager makes a patch’s lifetime visible in the test body:

from unittest.mock import patch

def test_label():
    with patch("service.fetch_record", autospec=True) as fetch_record:
        fetch_record.return_value = {"label": "sample"}
        assert service.label_for("r-17") == "SAMPLE"

A decorator is concise for one dependency across the whole test. For several patches, stack decorators; mocks are passed to the test in the reverse order of the decorators:

@patch("service.fetch_record")
@patch("service.write_audit")
def test_work(write_audit, fetch_record):
    ...

For patches started manually in a unittest.TestCase, register cleanup so the patch is stopped even if setup or the test fails:

def setUp(self):
    patcher = patch("service.fetch_record", autospec=True)
    self.fetch_record = patcher.start()
    self.addCleanup(patcher.stop)

Keep replacements scoped to the smallest practical region. A patch that leaks into another test can cause order-dependent failures.

8. Patch objects, classes, mappings, and properties

Patch an object attribute

with patch.object(client, "send", autospec=True) as send:
    send.return_value = {"ok": True}
    ...

Patch a class constructor

Patch the class where the code under test looks it up. Calls to the patched class return its mock return_value, which represents the constructed instance:

# worker.py imports Client and constructs Client()
@patch("worker.Client", autospec=True)
def test_worker(Client):
    instance = Client.return_value
    instance.send.return_value = {"ok": True}
    ...

Temporarily modify a mapping

import os
from unittest.mock import patch

with patch.dict(os.environ, {"MODE": "test"}):
    assert os.environ["MODE"] == "test"

Patch several attributes

patch.multiple can replace multiple attributes on one target. Use it when the grouped patch makes the test clearer; separate context managers can be easier to read for unrelated dependencies.

Properties

For a property, use PropertyMock on the class that provides the descriptor:

from unittest.mock import PropertyMock, patch

with patch.object(Model, "status", new_callable=PropertyMock) as status:
    status.return_value = "ready"
    assert Model().status == "ready"

Properties and descriptors interact with Python’s attribute lookup rules. If a property is central to the behavior, a small fake object can sometimes be clearer than patching the descriptor.

9. Mock asynchronous dependencies

When patch creates a replacement for an asynchronous function, it uses an AsyncMock by default in modern Python versions. Configure its result like a regular mock and assert that it was awaited:

from unittest import IsolatedAsyncioTestCase
from unittest.mock import patch

# service.py defines: async def fetch_record(record_id): ...
# and async def label_for(record_id):
#     record = await fetch_record(record_id)
#     return record["label"].upper()

class AsyncLabelTests(IsolatedAsyncioTestCase):
    @patch("service.fetch_record", autospec=True)
    async def test_label_for(self, fetch_record):
        fetch_record.return_value = {"label": "sample"}

        result = await service.label_for("r-17")

        self.assertEqual(result, "SAMPLE")
        fetch_record.assert_awaited_once_with("r-17")

Use assert_awaited_once_with to distinguish an awaited call from a coroutine that was created but never awaited. Async mocking details can vary by Python version; check the documentation for the interpreter used by your project.

10. When a fake is simpler than a mock

A mock is strongest when the test needs to configure a response or verify an interaction. For a small deterministic dependency, a handwritten fake may communicate the behavior more directly:

class MemoryGateway:
    def __init__(self, records):
        self.records = records

    def fetch_record(self, record_id):
        return self.records[record_id]

Use a fake when several tests need the same simple behavior or when a chain of mock configuration obscures the scenario. Use a mock when the interaction itself matters or when a temporary replacement is the simplest boundary.

11. Troubleshooting common failures

Symptom Likely cause Fix
The real dependency still runs The patch target is the definition module, while the code resolves an imported name elsewhere. Patch the lookup in the system-under-test module, such as service.fetch_record.
AttributeError when applying the patch The dotted target is misspelled or the attribute is not present. Check imports and the exact lookup expression. Avoid create=True unless the code intentionally creates that attribute; it can conceal a wrong target.
The mock returns another mock instead of expected data The test did not configure the relevant mock or chained return value. Set mock.return_value, or configure the specific child such as Client.return_value.send.return_value.
A call assertion fails despite similar arguments Positional and keyword arguments differ, or the mock was called more than once. Inspect call_args and call_args_list; assert the actual contract and expected call count.
TypeError with autospec The call does not match the real function signature. Fix the production call or intended test setup. If the API is dynamic, consider a narrower spec or fake.
Autospec says a valid dynamic attribute is missing The attribute is created at runtime and is not visible during introspection. Use a spec based on an instance where appropriate, configure a narrower mock, or use a fake.
Unexpected StopIteration An iterable side_effect was exhausted. Provide enough outcomes or use a function side effect for repeated behavior.
Async result is not the expected value The code did not await the mock, or the test asserted a call rather than an await. Await the dependency in production code and use assert_awaited_... assertions.
Tests pass alone but fail in a suite A manually started patch was not stopped or shared state remains modified. Use a context manager/decorator or register addCleanup(patcher.stop).

12. Performance, reliability, and maintenance

Mocks are local Python objects, so their runtime cost is usually not the bottleneck in a unit test; extensive nested configuration and introspection can add overhead and, more importantly, make tests harder to maintain. Autospec introspects the target as attributes are accessed, so avoid applying it indiscriminately to objects with expensive or side-effecting descriptors.

Reliability comes from narrow patch scopes, patching the correct lookup, and configuring only behavior relevant to the case. Mocks isolate unit behavior, but they cannot prove that the real network client, filesystem, or third-party API integrates correctly. Keep suitable integration coverage for those boundaries. Avoid asserting incidental implementation details so refactors do not break tests that still represent correct behavior.

13. FAQ

Does unittest.mock need to be installed?

No. It is part of Python’s standard library as unittest.mock.

What is the difference between Mock and MagicMock?

MagicMock includes common magic methods for Python protocols; use it when the code relies on operations such as indexing or iteration. Use Mock for ordinary callables and attributes.

Does patch restore the original object?

Yes. A patch used as a decorator or context manager is undone when its scope exits. Manually started patchers must be stopped, preferably through test cleanup.

Should every dependency be mocked?

No. Mock boundaries that make a unit test deterministic or let it verify an important interaction. A real small object or fake can make the test simpler when no isolation benefit is needed.

Or skip the browser setup

If a test workflow also needs real website screenshots, ScreenshotNeo is a website screenshot API and MCP server. Its one-call API can return an image or PDF, without setting up a browser in your project. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for ScreenshotNeo’s free plan.