Isolation & Contracts

create_autospec vs patch(autospec=True)

Both calls produce a double bound to a real interface, and teams routinely use them interchangeably until a test starts passing a class mock where an instance was expected. They are not alternatives to each other — they answer different questions. patch(autospec=True) replaces a name the code under test looks up; create_autospec hands you an object to pass in yourself.

Prerequisites

  • Python 3.8+; create_autospec gained proper async support in 3.8 and instance=True behaves as described throughout.
  • The spec model from autospec and strict mocking.

Solution

Choose by how the collaborator reaches the code, not by preference.

Python
from unittest.mock import create_autospec, patch
from myapp.gateway import PaymentGateway
from myapp import orders            # orders.py does: from myapp.gateway import PaymentGateway


# 1. The code looks the name up itself -> patch it where it is used.
def test_charge_calls_the_gateway():
    with patch.object(orders, "PaymentGateway", autospec=True) as MockGateway:
        instance = MockGateway.return_value       # what orders.py will construct
        instance.charge.return_value = {"id": "ch_1"}

        orders.place_order(amount=100)

        instance.charge.assert_called_once_with(amount=100, currency="EUR")


# 2. The code receives the collaborator -> build the double and inject it.
def test_charge_with_injected_gateway():
    gateway = create_autospec(PaymentGateway, instance=True)   # an INSTANCE double
    gateway.charge.return_value = {"id": "ch_1"}

    service = OrderService(gateway=gateway)                    # constructor injection
    service.place_order(amount=100)

    gateway.charge.assert_called_once_with(amount=100, currency="EUR")

instance=True is the detail that causes the most confusion. Without it, create_autospec(PaymentGateway) returns a class mock: callable, with return_value being the instance double. Pass that where an instance is expected and every attribute access resolves on the wrong object — the test still passes, and it asserts nothing about the real interface. With instance=True, the double is the instance and calling it raises TypeError, which turns the mistake into a failure.

Both forms enforce signatures, and that enforcement is the whole reason to use either:

Python
gateway = create_autospec(PaymentGateway, instance=True)
gateway.charge(amount=100, currency="EUR")          # fine
gateway.charge(100, "EUR", retries=3)               # TypeError: unexpected keyword
gateway.refund_all()                                # AttributeError: no such method
Four ways to build a specced double A table comparing create_autospec on a class, create_autospec with instance True, patch with autospec True, and Mock with a spec argument, across what each returns, whether signatures are enforced, and where each belongs. Four ways to build a specced double Criterion Returns Signatures create_autospec(Cls) a class mock enforced create_autospec(Cls, instance=True) an instance double enforced patch(target, autospec=True) installs a class mock enforced Mock(spec=Cls) a shallow double names only
Only the first three copy real signatures; Mock(spec=...) checks attribute names and lets any call shape through.

Why this works

create_autospec walks the spec object with dir(), and for every callable it finds it copies the real __signature__ onto the child mock, recursing into nested attributes. The resulting double therefore rejects both unknown names and wrong call shapes. patch(..., autospec=True) does exactly the same construction, then performs the additional work of setattr-ing the double over the target and restoring the original in a finally. The instance flag decides which end of the class/instance pair you receive: a class mock is callable and returns the instance double, and an instance double is not callable at all.

The class mock and the instance double A sequence diagram with three lanes: the test, the class mock produced by patch, and the instance double. The test configures the instance double through return_value, the code under test constructs the class which yields that same instance, and the assertions afterwards are made against the instance rather than the class. The class mock and the instance double test class mock instance double patch(..., autospec=True) return_value is the instance configure charge() assertions land here Passing the class mock where an instance is expected is the classic silent-pass bug.
With patch you always configure and assert on return_value; with instance=True there is no class mock in the picture at all.

Making the choice mechanical

The decision follows from one question: does the code under test obtain the collaborator, or receive it?

Code that imports a name and constructs it must be patched, because there is no seam to inject through. Code that takes the collaborator as a constructor or function argument should be given a double directly — patching it would be reaching past a seam that already exists, and the resulting test is harder to read and more fragile.

That framing also explains why a codebase using dependency injection for testability needs very little patching: the seams make create_autospec sufficient almost everywhere, and patch is reserved for third-party globals and module-level singletons that cannot be injected.

Python
# No seam: the function reaches for the name itself.
def send_receipt(order):
    client = SmtpClient(host=settings.SMTP_HOST)     # must be patched
    client.send(order.email, render(order))

# Seam present: the collaborator arrives as an argument.
def send_receipt(order, client):                     # inject a create_autospec double
    client.send(order.email, render(order))

Where both are possible, prefer injection. patch binds the test to the module layout — move the import and the test breaks even though the behaviour did not — while an injected double binds only to the interface, which is the thing the test is actually about. That coupling to module structure is the same one described in where to patch.

Cost and caching

Autospec is not free. Building the double introspects the class and every attribute it exposes, and for a large class — an ORM model, a client with a hundred methods, a module object — that introspection can take milliseconds, paid per construction. In a suite that builds the same double in five hundred tests, the cost is visible in --durations.

Two mitigations, in order of preference. Build the double once in a session-scoped fixture and reset it per test, which keeps the introspection cost singular:

Python
import pytest
from unittest.mock import create_autospec
from myapp.gateway import PaymentGateway

@pytest.fixture(scope="session")
def _gateway_template():
    return create_autospec(PaymentGateway, instance=True)   # introspected once

@pytest.fixture
def gateway(_gateway_template):
    _gateway_template.reset_mock(return_value=True, side_effect=True)
    return _gateway_template                                 # cheap per test

Or spec a narrower object: if the code only uses two methods, autospec a small Protocol describing those two rather than the full class. That is faster, and it documents the actual dependency — a class with a hundred methods being autospecced usually means the code depends on far less than it appears to.

Do not reach for Mock(spec=...) as the cheap option. It skips signature copying, which is the property that makes autospec worth having; the saving is real but it buys back exactly the failure mode you were preventing.

Verifying the double really is specced

A double that silently lost its spec is worse than a plain mock, because the test reads as strict and behaves as permissive. Two one-line checks catch it.

Python
from unittest.mock import create_autospec
from myapp.gateway import PaymentGateway

gateway = create_autospec(PaymentGateway, instance=True, spec_set=True)

assert gateway._spec_class is PaymentGateway      # bound to the real class
assert not callable(gateway)                      # it is an instance, not a class mock

The second assertion is the one worth keeping in a shared fixture: callable(double) is True for a class mock and False for an instance double, so it fails loudly the day someone drops instance=True while refactoring.

At review time, three signals distinguish a real autospec double from a decorative one. The spec argument is a real imported class rather than a string; instance=True is present whenever the code expects an instance; and no test attaches attributes the class does not declare — a line like double.internal_cache = {} next to a spec_set double raises immediately, and next to a plain spec double silently creates state that the real object never has.

Three checks that a double still matches its class A left-to-right check sequence: confirm the spec class is the real one, confirm callable-ness matches instance or class expectations, call with a wrong signature to confirm TypeError, and confirm an unknown attribute raises. Three checks that a double still matches its class spec class bound to the real class callable? instance vs class mock wrong signature must raise TypeError unknown attr must raise AttributeError
All four are cheap assertions; together they prove the double is enforcing a contract rather than pretending to.

Edge cases and failure modes

  • patch(autospec=True) on a function target replaces the function, so the mock receives the same arguments the function would, including self for unbound methods — a frequent surprise when patching methods on a class rather than on an instance.
  • Properties become plain attributes under both forms; observing access requires PropertyMock, as covered in mocking properties and class attributes with autospec.
  • Attributes created only in __init__ are invisible to introspection of the class, so the double lacks them until you assign them explicitly.
  • autospec=True follows __getattr__-based dynamic APIs poorly. A client that generates methods at runtime has nothing for dir() to find, and the double rejects every call the real object would accept.
  • Async methods produce AsyncMock children automatically on Python 3.8+, but only when the spec really declares them async def; a sync method returning a coroutine is not detected.
  • spec_set is orthogonal and worth adding. Both forms accept it, and it closes the write path so a typo'd attribute assignment fails instead of silently creating state.

A note on decorator stacking, since patch is most often used as one. Multiple @patch decorators apply bottom-up, so the mock arguments arrive in the reverse of the order they are written, and mixing them with pytest fixtures means the mocks come first in the signature. That ordering is the single most common source of a test where two doubles are configured on the wrong objects — and because both are mocks, nothing fails until an assertion checks the wrong one. Using patch as a context manager inside the test body removes the ordering question entirely, at the cost of one indent level, which is usually a good trade for anything beyond two patches.

Frequently Asked Questions

Are create_autospec and patch(autospec=True) the same thing? They build the same kind of double. The difference is placement: create_autospec returns an object you pass somewhere yourself, while patch(autospec=True) builds one and installs it over a named target for the duration of the block.

When do I need instance=True? When the code under test receives an instance rather than a class. create_autospec(Cls) returns a callable class mock whose return_value is the instance double; create_autospec(Cls, instance=True) returns the instance double directly and refuses to be called.

Why is autospec slow on large classes? Because it introspects the whole object graph at creation time, recursively specced for each attribute. On a class with hundreds of members, or one whose attribute access triggers imports, that cost is paid per call — cache the spec in a fixture rather than rebuilding it per test.

Does autospec catch wrong keyword arguments? Yes. The double copies the real signature, so a call with an unknown keyword or the wrong arity raises TypeError at call time, which a plain Mock accepts silently.

← Back to Autospec & Strict Mocking