Why does a bare unittest.mock.Mock accept calls to methods the real object lacks?
answer
- The double has no interface of its own
- Unknown names are created, not rejected
- Cached children make the assertion look real
- Pin the double to a real class
- spec constrains reads, spec_set also writes
basics
~20 sA Mock invents a child mock for any attribute name on first touch, so a typo or a renamed method still returns something callable and the test passes. spec= or spec_set= pins it to a real object's attributes.
solid answer
~40 sAttribute access on a `unittest.mock.Mock` is total: an unknown name is created as a child mock, cached and returned, so `client.post_entry` and `client.post_entrie` both succeed and both record calls. Rename a method in production and a suite built on bare mocks stays green, because the test is only agreeing with itself. `Mock(spec=Ledger)` copies the attribute names from the real class or instance and raises `AttributeError` for anything outside that set, while faking `__class__` so `isinstance` still passes. `spec_set=` is stricter and also refuses *assignment* of names the real object lacks. Neither checks call signatures, and the children of a specced mock are unconstrained — that is `unittest.mock.create_autospec`'s job. For a double with no importable interface, configure it and then call `unittest.mock.seal` so that anything invented afterwards raises AttributeError.
code
python · 21 linesfrom unittest.mock import Mock, seal
class Ledger:
def post_entry(self, amount, account): ...
loose = Mock()
loose.post_entrie(100, "cash") # typo invented silently; the test still passes
specced = Mock(spec=Ledger)
try:
specced.post_entrie
except AttributeError as exc:
print(exc) # Mock object has no attribute 'post_entrie'
config = Mock()
config.db.url = "sqlite://"
seal(config)
try:
config.db.port # nothing new may be invented after sealing
except AttributeError as exc:
print(exc)go deeper
Recall the headline: a mock without spec will answer to any attribute name you invent, so a typo in a test does not fail. Passing spec= with the real class makes unknown names raise AttributeError instead.
Explain the mechanics: unknown attributes are created as child mocks and cached, spec= restricts the name set and fakes class, spec_set= additionally blocks assignment, and neither one checks how the method is called.
Demonstrate the production judgment. Be ready to describe how a renamed collaborator method leaves a fully-mocked suite green, and to prescribe a convention: spec or autospec every double, seal what cannot be specced, assert on calls rather than invented values.
Own the tradeoff at suite scale. Decide how much of the suite may use unconstrained doubles at all, where contract or integration coverage has to backstop mocked boundaries, and how that policy is enforced without turning every test into boilerplate.
## A Mock has no interface A `unittest.mock.Mock` has no interface. Every attribute you touch that it has not seen before is created on the spot as a **child mock**, stored, and returned; the child is itself a Mock, so it is callable, and calling it returns another mock. Attribute access is therefore total: no name is ever wrong. `mock.post_entry`, `mock.post_entrie` and `mock.transmogrify` all succeed, all return something callable, and all record their calls. This is what makes a Mock so cheap to use — and it is the single largest source of false confidence in Python test suites. Concretely: a payment reconciliation job depends on a ledger client whose method is `post_entry`. Someone renames it to `record_entry` and updates the production call sites. The unit tests replace the client with a bare `Mock`, so nothing in the 27-minute suite notices: - the tests still call `client.post_entry(...)` on a double that happily invents it, - `assert_called_once_with` still passes on the child that got created, - and the suite stays green while the real object no longer has that method. The tests were never checking the interface; they were checking that the test agreed with itself. Note also that child mocks are cached, so `mock.post_entry is mock.post_entry` — the assertion sees exactly the child the code touched, which is precisely why the illusion is so convincing. ## `spec=` and `spec_set=` `spec=` closes the name hole. `Mock(spec=Ledger)` copies the attribute names from the object or class you pass in (using `dir()`), and any access outside that set raises `AttributeError: Mock object has no attribute 'post_entrie'` at the moment of access, not at the assertion. It also fakes `__class__`, so `isinstance(Mock(spec=Ledger), Ledger)` is True and production code that type-checks its collaborator still works — a useful lie, though `type(mock)` remains a Mock subclass. You can spec against a class or against a live instance; against an instance you also pick up attributes assigned in `__init__`, which a class-based spec cannot see. `spec_set=` is the stricter sibling. - `spec=` only constrains *reads*: you can still write `mock.newattr = 1` and invent an attribute on the double, which reintroduces the typo problem in the arrange phase. - `spec_set=` also constrains *writes*, so assigning a name the real object does not have raises AttributeError. Mock's own configuration attributes — `return_value`, `side_effect` and the rest — remain settable. There is essentially no reason to prefer `spec=` over `spec_set=` for a double you fully control; the looser form exists mostly for doubles that need extra bookkeeping attributes hung on them. ## What a spec does not check What `spec=` does **not** do is check how you call things. It validates the attribute name and stops. `Mock(spec=Ledger).post_entry(1, 2, 3, 4)` passes even though the real method takes two arguments, and the children handed out by a specced mock are ordinary unconstrained mocks, so one level down you are back to inventing names. Signature checking and recursive constraint are `unittest.mock.create_autospec` and `patch(..., autospec=True)`; a spec is a **shallow, name-level fence**. ## Sealing a hand-built double `unittest.mock.seal` covers the case where there is no interface to spec against — a double you assembled by hand for a config object, a dynamically built client, a nested tree of `return_value` chains. You build exactly the structure the test needs, call `seal(mock)`, and from then on any *new* auto-created attribute anywhere in that tree raises AttributeError naming the full path, such as `mock.db.port`. Attributes you configured before sealing keep working. So `spec` pins a mock to a real class up front; `seal` freezes whatever you actually built. Sealing recurses into child mocks that were created by attribute access, which is what makes it useful on a deep tree. ## The interview point The interview point beneath all of this is that a mock's permissiveness converts a compile-time-ish error — calling a method that does not exist — into a green test. Static checkers help but do not see through a Mock, whose type is deliberately anything. So the discipline is: 1. spec every double against the real collaborator, 2. prefer `spec_set` or autospec, 3. seal what you cannot spec, 4. and remember that even a perfectly specced mock only tells you the *shape* of the local version of the dependency was respected. One more practical wrinkle: mock names starting with `assert` are guarded — a Mock refuses to invent an attribute like `assert_called_onece` and raises AttributeError instead, so that misspelled assertions fail rather than silently passing. That guard covers only the assertion family; every other misspelled name is still invented, which is why spec remains the real defence.
- Does isinstance still work against a mock created with spec=?Yes. A specced mock fakes `__class__` to report the specced class, so `isinstance(Mock(spec=Ledger), Ledger)` is True and production code that type-checks its collaborator keeps working. It is a deliberate lie confined to that one check: `type(mock)` is still a Mock subclass, and nothing else about the real class is inherited.
- When would you reach for unittest.mock.seal instead of spec=?When there is no interface to spec against — a hand-assembled configuration object, a deep tree of chained return values, a client built dynamically. You configure exactly the attributes the test needs, call `seal(mock)`, and any *new* attribute invented afterwards anywhere in the tree raises AttributeError naming the path, such as `mock.db.port`. spec pins a mock to a real class up front; seal freezes whatever you actually built.
- What is the difference between speccing against a class and against a live instance?A class-based spec sees only what the class body defines, so attributes assigned in `__init__` are missing and accessing them on the double raises AttributeError. Speccing against a constructed instance picks those up, at the cost of needing a real instance in the test. Where neither is convenient, spec against a narrow explicit interface such as a Protocol or abstract base class.
An unspecced mock is a yes-man with a perfect memory: it agrees to every request, remembers it exactly, and lets you prove afterwards that you asked for something nobody real could have provided.
saying these in an interview costs you the question
- Thinks a bare Mock raises AttributeError for unknown names
- Believes spec= also validates call arguments
- Says spec= and spec_set= mean the same thing
- Assumes each attribute access returns a fresh, unrelated mock
- Thinks isinstance always fails against a specced mock
- Treats a green fully-mocked suite as proof the interface still matches