Batu Lab NotesPractical developer guides

Use spec_set to reject accidental mock attributes

By Batu ยท English technical notes

Also published in our primary archive.

Use spec_set to reject accidental mock attributes

Use spec_set when a test must reject both reading and assigning names absent from the replaced object. A regular spec prevents an invalid attribute read, but it does not prevent a later assignment from creating an accidental attribute on the mock.

The small Settings instance exposes only region. First, Mock(spec=settings) correctly raises AttributeError when the fixture reads misspelled. That guarantee can appear sufficient until the test assigns misspelled = "enabled". The assignment succeeds, and the mock now holds that test-only name; no production Settings instance was consulted.

The spec_set mock uses the same instance. It permits assignment to the real region attribute, then rejects both the absent read and absent write. The output reports the distinction without printing implementation-dependent mock representations. Assertions make the intended boundary explicit.

This does not validate attribute types, property side effects, or the behavior behind a method. It only limits a mock's attribute surface based on the supplied spec at construction time. Use an instance rather than a class when the interface contains attributes created in __init__. Mock and spec_set are part of unittest.mock, added to the standard library in Python 3.3; see the official Mock reference.

AI assistance disclosure: this article and synthetic example were drafted with AI assistance.

from unittest.mock import Mock


class Settings:
    def __init__(self):
        self.region = "eu"


settings = Settings()
read_limited = Mock(spec=settings)

try:
    read_limited.misspelled
except AttributeError:
    spec_read_rejected = True
else:
    spec_read_rejected = False
assert spec_read_rejected

read_limited.misspelled = "enabled"
assert read_limited.misspelled == "enabled"

read_write_limited = Mock(spec_set=settings)
read_write_limited.region = "us"
assert read_write_limited.region == "us"

try:
    read_write_limited.misspelled = "enabled"
except AttributeError:
    spec_set_write_rejected = True
else:
    spec_set_write_rejected = False
assert spec_set_write_rejected

print("spec absent read: AttributeError")
print("spec absent write: accepted")
print("spec_set absent write: AttributeError")
spec absent read: AttributeError
spec absent write: accepted
spec_set absent write: AttributeError