[{"data":1,"prerenderedAt":727},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fmocking-properties-and-class-attributes-with-autospec\u002F":3},{"id":4,"title":5,"body":6,"description":693,"extension":694,"meta":695,"navigation":136,"path":723,"seo":724,"stem":725,"__hash__":726},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fmocking-properties-and-class-attributes-with-autospec\u002Findex.md","Mocking Properties and Class Attributes with Autospec",{"type":7,"value":8,"toc":685},"minimark",[9,45,50,86,90,108,465,469,516,520,619,623,629,635,641,645,675,681],[10,11,12,16,17,20,21,24,25,28,29,32,33,37,38,40,41,44],"p",{},[13,14,15],"code",{},"create_autospec"," is the strictest tool in ",[13,18,19],{},"unittest.mock"," for binding a test double to a real interface, but engineers are routinely surprised when a ",[13,22,23],{},"@property"," on the spec class becomes a writable plain attribute on the mock — assigning ",[13,26,27],{},"mock.value = 10"," works, yet the original getter never runs and ",[13,30,31],{},"PropertyMock","-style access tracking is gone. This happens because autospec inspects the class through the descriptor protocol and freezes each member's ",[34,35,36],"em",{},"evaluated"," shape, collapsing computed properties into static attribute slots. This guide shows how to mock properties and class attributes correctly: when to set attributes directly on an autospec instance, when ",[13,39,31],{}," is mandatory, and how ",[13,42,43],{},"spec_set"," locks the surface in both directions.",[46,47,49],"h2",{"id":48},"prerequisites","Prerequisites",[51,52,53,64,69],"ul",{},[54,55,56,57,60,61,63],"li",{},"Python 3.8+ (",[13,58,59],{},"AsyncMock"," and the modern ",[13,62,15],{}," attribute handling). Examples target 3.11.",[54,65,66,68],{},[13,67,19],{}," from the standard library — no third-party packages.",[54,70,71,72,75,76,79,80,85],{},"Familiarity with the descriptor protocol (",[13,73,74],{},"__get__","\u002F",[13,77,78],{},"__set__",") and with ",[81,82,84],"a",{"href":83},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002F","autospec strict mocking"," fundamentals.",[46,87,89],{"id":88},"solution","Solution",[10,91,92,93,95,96,100,101,103,104,107],{},"The core rule: ",[13,94,15],{}," records a property as a ",[97,98,99],"strong",{},"plain attribute"," you assign to directly. Use ",[13,102,31],{}," only when the ",[34,105,106],{},"act of accessing"," the attribute must be observed or must raise.",[109,110,115],"pre",{"className":111,"code":112,"language":113,"meta":114,"style":114},"language-python shiki shiki-themes github-light github-dark","import unittest.mock as mock\nfrom unittest.mock import create_autospec, PropertyMock, patch\n\n\nclass Account:\n    def __init__(self, balance: int) -> None:\n        self._balance = balance\n\n    @property\n    def balance(self) -> int:               # computed getter on the real class\n        return self._balance\n\n    @property\n    def is_overdrawn(self) -> bool:         # derived property with logic\n        return self._balance \u003C 0\n\n    def withdraw(self, amount: int) -> int:\n        self._balance -= amount\n        return self._balance\n\n\n# 1. Autospec the class. Methods are bound to real signatures; unknown\n#    attributes raise AttributeError. Properties become plain attributes.\nMockAccount = create_autospec(Account, spec_set=True)\ninstance = MockAccount(balance=100)         # __init__ signature is enforced\n\n# 2. Set the property value DIRECTLY — autospec exposes `balance` as a\n#    writable attribute, NOT as a descriptor. The real getter never runs.\ninstance.balance = 250\nassert instance.balance == 250              # plain read of the stored value\n\n# 3. Methods still enforce their signature thanks to autospec.\ninstance.withdraw(50)                       # OK: matches (self, amount)\ntry:\n    instance.withdraw(1, 2, 3)              # wrong arity -> caught at call time\nexcept TypeError as exc:\n    print(\"signature enforced:\", exc)\n\n# 4. When the code under test must OBSERVE property access — or the getter\n#    must raise — attach a PropertyMock to the *type*, not the instance.\ntype(instance).is_overdrawn = PropertyMock(return_value=True)\nassert instance.is_overdrawn is True\ntype(instance).is_overdrawn.assert_called_once()   # access was tracked\n\n# 5. A PropertyMock can raise on read, modelling a getter that errors.\ntype(instance).is_overdrawn = PropertyMock(side_effect=RuntimeError(\"stale\"))\ntry:\n    _ = instance.is_overdrawn               # access triggers the side effect\nexcept RuntimeError as exc:\n    print(\"getter raised on access:\", exc)\n\n\n# Patching a property on a real (non-autospec) object also requires\n# PropertyMock bound to the class, because data descriptors live on the type.\ndef test_patch_real_property():\n    real = Account(balance=100)\n    with patch.object(Account, \"balance\", new_callable=PropertyMock) as m_balance:\n        m_balance.return_value = 999\n        assert real.balance == 999          # patched getter wins over _balance\n        m_balance.assert_called_once_with()\n","python","",[13,116,117,125,131,138,143,149,155,161,166,172,178,184,189,194,200,206,211,217,223,228,233,238,244,250,256,262,267,273,279,285,291,296,302,308,314,320,326,332,337,343,349,355,361,367,372,378,384,389,395,401,407,412,417,423,429,435,441,447,453,459],{"__ignoreMap":114},[118,119,122],"span",{"class":120,"line":121},"line",1,[118,123,124],{},"import unittest.mock as mock\n",[118,126,128],{"class":120,"line":127},2,[118,129,130],{},"from unittest.mock import create_autospec, PropertyMock, patch\n",[118,132,134],{"class":120,"line":133},3,[118,135,137],{"emptyLinePlaceholder":136},true,"\n",[118,139,141],{"class":120,"line":140},4,[118,142,137],{"emptyLinePlaceholder":136},[118,144,146],{"class":120,"line":145},5,[118,147,148],{},"class Account:\n",[118,150,152],{"class":120,"line":151},6,[118,153,154],{},"    def __init__(self, balance: int) -> None:\n",[118,156,158],{"class":120,"line":157},7,[118,159,160],{},"        self._balance = balance\n",[118,162,164],{"class":120,"line":163},8,[118,165,137],{"emptyLinePlaceholder":136},[118,167,169],{"class":120,"line":168},9,[118,170,171],{},"    @property\n",[118,173,175],{"class":120,"line":174},10,[118,176,177],{},"    def balance(self) -> int:               # computed getter on the real class\n",[118,179,181],{"class":120,"line":180},11,[118,182,183],{},"        return self._balance\n",[118,185,187],{"class":120,"line":186},12,[118,188,137],{"emptyLinePlaceholder":136},[118,190,192],{"class":120,"line":191},13,[118,193,171],{},[118,195,197],{"class":120,"line":196},14,[118,198,199],{},"    def is_overdrawn(self) -> bool:         # derived property with logic\n",[118,201,203],{"class":120,"line":202},15,[118,204,205],{},"        return self._balance \u003C 0\n",[118,207,209],{"class":120,"line":208},16,[118,210,137],{"emptyLinePlaceholder":136},[118,212,214],{"class":120,"line":213},17,[118,215,216],{},"    def withdraw(self, amount: int) -> int:\n",[118,218,220],{"class":120,"line":219},18,[118,221,222],{},"        self._balance -= amount\n",[118,224,226],{"class":120,"line":225},19,[118,227,183],{},[118,229,231],{"class":120,"line":230},20,[118,232,137],{"emptyLinePlaceholder":136},[118,234,236],{"class":120,"line":235},21,[118,237,137],{"emptyLinePlaceholder":136},[118,239,241],{"class":120,"line":240},22,[118,242,243],{},"# 1. Autospec the class. Methods are bound to real signatures; unknown\n",[118,245,247],{"class":120,"line":246},23,[118,248,249],{},"#    attributes raise AttributeError. Properties become plain attributes.\n",[118,251,253],{"class":120,"line":252},24,[118,254,255],{},"MockAccount = create_autospec(Account, spec_set=True)\n",[118,257,259],{"class":120,"line":258},25,[118,260,261],{},"instance = MockAccount(balance=100)         # __init__ signature is enforced\n",[118,263,265],{"class":120,"line":264},26,[118,266,137],{"emptyLinePlaceholder":136},[118,268,270],{"class":120,"line":269},27,[118,271,272],{},"# 2. Set the property value DIRECTLY — autospec exposes `balance` as a\n",[118,274,276],{"class":120,"line":275},28,[118,277,278],{},"#    writable attribute, NOT as a descriptor. The real getter never runs.\n",[118,280,282],{"class":120,"line":281},29,[118,283,284],{},"instance.balance = 250\n",[118,286,288],{"class":120,"line":287},30,[118,289,290],{},"assert instance.balance == 250              # plain read of the stored value\n",[118,292,294],{"class":120,"line":293},31,[118,295,137],{"emptyLinePlaceholder":136},[118,297,299],{"class":120,"line":298},32,[118,300,301],{},"# 3. Methods still enforce their signature thanks to autospec.\n",[118,303,305],{"class":120,"line":304},33,[118,306,307],{},"instance.withdraw(50)                       # OK: matches (self, amount)\n",[118,309,311],{"class":120,"line":310},34,[118,312,313],{},"try:\n",[118,315,317],{"class":120,"line":316},35,[118,318,319],{},"    instance.withdraw(1, 2, 3)              # wrong arity -> caught at call time\n",[118,321,323],{"class":120,"line":322},36,[118,324,325],{},"except TypeError as exc:\n",[118,327,329],{"class":120,"line":328},37,[118,330,331],{},"    print(\"signature enforced:\", exc)\n",[118,333,335],{"class":120,"line":334},38,[118,336,137],{"emptyLinePlaceholder":136},[118,338,340],{"class":120,"line":339},39,[118,341,342],{},"# 4. When the code under test must OBSERVE property access — or the getter\n",[118,344,346],{"class":120,"line":345},40,[118,347,348],{},"#    must raise — attach a PropertyMock to the *type*, not the instance.\n",[118,350,352],{"class":120,"line":351},41,[118,353,354],{},"type(instance).is_overdrawn = PropertyMock(return_value=True)\n",[118,356,358],{"class":120,"line":357},42,[118,359,360],{},"assert instance.is_overdrawn is True\n",[118,362,364],{"class":120,"line":363},43,[118,365,366],{},"type(instance).is_overdrawn.assert_called_once()   # access was tracked\n",[118,368,370],{"class":120,"line":369},44,[118,371,137],{"emptyLinePlaceholder":136},[118,373,375],{"class":120,"line":374},45,[118,376,377],{},"# 5. A PropertyMock can raise on read, modelling a getter that errors.\n",[118,379,381],{"class":120,"line":380},46,[118,382,383],{},"type(instance).is_overdrawn = PropertyMock(side_effect=RuntimeError(\"stale\"))\n",[118,385,387],{"class":120,"line":386},47,[118,388,313],{},[118,390,392],{"class":120,"line":391},48,[118,393,394],{},"    _ = instance.is_overdrawn               # access triggers the side effect\n",[118,396,398],{"class":120,"line":397},49,[118,399,400],{},"except RuntimeError as exc:\n",[118,402,404],{"class":120,"line":403},50,[118,405,406],{},"    print(\"getter raised on access:\", exc)\n",[118,408,410],{"class":120,"line":409},51,[118,411,137],{"emptyLinePlaceholder":136},[118,413,415],{"class":120,"line":414},52,[118,416,137],{"emptyLinePlaceholder":136},[118,418,420],{"class":120,"line":419},53,[118,421,422],{},"# Patching a property on a real (non-autospec) object also requires\n",[118,424,426],{"class":120,"line":425},54,[118,427,428],{},"# PropertyMock bound to the class, because data descriptors live on the type.\n",[118,430,432],{"class":120,"line":431},55,[118,433,434],{},"def test_patch_real_property():\n",[118,436,438],{"class":120,"line":437},56,[118,439,440],{},"    real = Account(balance=100)\n",[118,442,444],{"class":120,"line":443},57,[118,445,446],{},"    with patch.object(Account, \"balance\", new_callable=PropertyMock) as m_balance:\n",[118,448,450],{"class":120,"line":449},58,[118,451,452],{},"        m_balance.return_value = 999\n",[118,454,456],{"class":120,"line":455},59,[118,457,458],{},"        assert real.balance == 999          # patched getter wins over _balance\n",[118,460,462],{"class":120,"line":461},60,[118,463,464],{},"        m_balance.assert_called_once_with()\n",[46,466,468],{"id":467},"why-this-works","Why this works",[10,470,471,472,475,476,479,480,483,484,487,488,75,490,492,493,495,496,499,500,503,504,507,508,511,512,515],{},"Autospec walks the spec object with ",[13,473,474],{},"dir()"," and ",[13,477,478],{},"getattr",", and for a ",[13,481,482],{},"property"," it reads the descriptor's evaluated value (typically ",[13,485,486],{},"None"," or a sample), then stores a child mock in a normal attribute slot — the descriptor's ",[13,489,74],{},[13,491,78],{}," machinery is discarded. That is why direct assignment works and getter side effects vanish. ",[13,494,31],{}," reinstates descriptor behaviour: it is itself a descriptor, so it must live on the ",[34,497,498],{},"class"," (",[13,501,502],{},"type(instance)","), where Python's attribute lookup consults data descriptors before the instance ",[13,505,506],{},"__dict__",". ",[13,509,510],{},"spec_set=True"," then forbids creating attributes the real class never declared, turning interface drift into an immediate ",[13,513,514],{},"AttributeError",".",[46,517,519],{"id":518},"edge-cases-and-failure-modes","Edge cases and failure modes",[51,521,522,548,573,587,605],{},[54,523,524,530,531,534,535,538,539,541,542,544,545,515],{},[97,525,526,527,529],{},"Assigning a ",[13,528,31],{}," to the instance does nothing."," ",[13,532,533],{},"instance.prop = PropertyMock(...)"," just stores the mock object as a value; reading ",[13,536,537],{},"instance.prop"," returns the ",[13,540,31],{}," itself, never calling it. Always bind to ",[13,543,502],{}," or use ",[13,546,547],{},"patch.object(Class, \"prop\", new_callable=PropertyMock)",[54,549,550,555,556,558,559,561,562,565,566,569,570,572],{},[97,551,552,554],{},[13,553,43],{}," rejects derived test-only attributes."," If your test wants to stash a helper flag on the mock, ",[13,557,510],{}," raises ",[13,560,514],{},". Use plain ",[13,563,564],{},"spec"," (via ",[13,567,568],{},"create_autospec(Cls)"," without ",[13,571,43],{},") when you need to attach scratch attributes.",[54,574,575,578,579,582,583,586],{},[97,576,577],{},"Class-level attributes vs instance attributes diverge."," Autospec mirrors attributes defined at class scope onto both the class mock and its ",[13,580,581],{},"return_value",". A value set only inside ",[13,584,585],{},"__init__"," may not appear unless an instance-level sample existed during introspection; assign it explicitly on the instance mock.",[54,588,589,597,598,601,602,604],{},[97,590,591,475,593,596],{},[13,592,31],{},[13,594,595],{},"assert_called_with"," arguments."," A property getter takes no arguments, so assert with ",[13,599,600],{},"assert_called_once_with()"," (empty); setters invoked via ",[13,603,31],{}," receive the assigned value as the sole positional argument.",[54,606,607,610,611,613,614,618],{},[97,608,609],{},"Autospec does not deep-spec the property's return type."," The value you get back is a generic child mock, not an autospec of the declared return class. If callers chain methods on the returned value, wrap it with another ",[13,612,15],{},". Pairing this with ",[81,615,617],{"href":616},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fresolving-side_effect-and-return_value-conflicts\u002F","resolving side_effect and return_value conflicts"," avoids surprises when a getter both returns and raises.",[46,620,622],{"id":621},"frequently-asked-questions","Frequently Asked Questions",[10,624,625,628],{},[97,626,627],{},"Why does create_autospec turn a property into a plain attribute mock?","\nAutospec inspects the class via the descriptor protocol and records the property's evaluated type as a static attribute, not as a descriptor. The resulting mock holds a child mock you assign to directly; it never calls the original getter or fires side effects on access.",[10,630,631,634],{},[97,632,633],{},"Do I need PropertyMock if I use create_autospec on a class?","\nOnly when the code under test depends on getter side effects or you must assert the property was accessed. For a value you can set directly, assign instance.attr on the autospec instance. Use PropertyMock when read access itself must be observed or must raise.",[10,636,637,640],{},[97,638,639],{},"What is the difference between spec and spec_set for attributes?","\nspec raises AttributeError when you read or call an attribute the real object lacks but still lets you set arbitrary new attributes. spec_set additionally raises AttributeError when you set an attribute the spec class does not define, locking the surface in both directions.",[46,642,644],{"id":643},"related-guides","Related guides",[51,646,647,654,660,668],{},[54,648,649,650,515],{},"The decision of which mock class backs your double matters here too: see ",[81,651,653],{"href":652},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fmock-vs-magicmock-vs-asyncmock-when-to-use-each\u002F","Mock vs MagicMock vs AsyncMock — when to use each",[54,655,656,657,659],{},"When properties and methods share configuration, ",[81,658,617],{"href":616}," explains the dispatch order.",[54,661,662,663,667],{},"For deeper attribute-resolution internals, the ",[81,664,666],{"href":665},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002F","Deep Dive into unittest.mock"," reference covers spec validation and child-mock caching.",[54,669,670,671,515],{},"If a hand-written stand-in would model the property more honestly, compare approaches in ",[81,672,674],{"href":673},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdependency-injection-for-testability\u002Finjecting-fakes-vs-mocks-in-constructors\u002F","injecting fakes vs mocks in constructors",[10,676,677,678],{},"← Back to ",[81,679,680],{"href":83},"Autospec & Strict Mocking",[682,683,684],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":114,"searchDepth":127,"depth":127,"links":686},[687,688,689,690,691,692],{"id":48,"depth":127,"text":49},{"id":88,"depth":127,"text":89},{"id":467,"depth":127,"text":468},{"id":518,"depth":127,"text":519},{"id":621,"depth":127,"text":622},{"id":643,"depth":127,"text":644},"Mock Python properties and class attributes correctly with create_autospec, PropertyMock, and spec_set — why autospec turns properties into plain attributes.","md",{"slug":696,"type":697,"breadcrumb":698,"datePublished":699,"dateModified":699,"faq":700,"howto":707},"mocking-properties-and-class-attributes-with-autospec","long_tail","Properties & Autospec","2026-06-18",[701,703,705],{"q":627,"a":702},"Autospec inspects the class via the descriptor protocol and records the property's evaluated type as a static attribute, not as a descriptor. The resulting mock holds a child mock you assign to directly; it never calls the original getter or fires side effects on access.",{"q":633,"a":704},"Only when the code under test depends on getter side effects or you must assert the property was accessed. For a value you can set directly, assign instance.attr on the autospec instance. Use PropertyMock when read access itself must be observed or must raise.",{"q":639,"a":706},"spec raises AttributeError when you read or call an attribute the real object lacks but still lets you set arbitrary new attributes. spec_set additionally raises AttributeError when you set an attribute the spec class does not define, locking the surface in both directions.",{"name":708,"description":709,"steps":710},"How to mock properties and class attributes with autospec","Replace a class with an autospec mock, set instance attributes directly, and attach a PropertyMock when getter access must be observed.",[711,714,717,720],{"name":712,"text":713},"Autospec the class","Call create_autospec(TargetClass) so methods and attributes are bound to the real signatures and undefined names raise AttributeError.",{"name":715,"text":716},"Set the attribute value","Assign the desired value to instance.attribute on the autospec return_value; autospec records properties as plain attributes.",{"name":718,"text":719},"Attach PropertyMock for access semantics","When the getter must raise or be asserted, patch type(instance).attribute with a PropertyMock configured with return_value or side_effect.",{"name":721,"text":722},"Lock the surface with spec_set","Pass spec_set=True to reject assignment of attributes the real class never defines, catching typos and interface drift.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fmocking-properties-and-class-attributes-with-autospec",{"title":5,"description":693},"advanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fmocking-properties-and-class-attributes-with-autospec\u002Findex","kLNPj0v25hlmeBBETVvykyHkGq5UbAH8g7lbWYrCPIM",1781793487984]