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_autospecgained proper async support in 3.8 andinstance=Truebehaves as described throughout. - The spec model from autospec and strict mocking.
Solution
Choose by how the collaborator reaches the code, not by preference.
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:
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
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.
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.
# 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:
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.
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.
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, includingselffor 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=Truefollows__getattr__-based dynamic APIs poorly. A client that generates methods at runtime has nothing fordir()to find, and the double rejects every call the real object would accept.- Async methods produce
AsyncMockchildren automatically on Python 3.8+, but only when the spec really declares themasync def; a sync method returning a coroutine is not detected. spec_setis 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.
Related
- Autospec and strict mocking — the contract enforcement both forms provide.
- Where to patch: understanding mock.patch targets — choosing the target when patching is the right answer.
- Dependency injection for testability — the seams that make
create_autospecsufficient. - assert_called_with vs call_args_list — asserting on the double once it is in place.
← Back to Autospec & Strict Mocking