How PropertyMock Intercepts Attribute Reads in Python
This article explains the internal mechanics behind Python’s
unittest.mock.PropertyMock and how it intercepts attribute
reads on mocked objects. While standard Mock objects return
callable sub-mocks upon attribute access, PropertyMock uses
Python's descriptor protocol to execute custom logic on access without
requiring explicit function call syntax. By defining a custom
__get__ method and attaching itself to a class definition,
PropertyMock mimics dynamic attributes, properties, and
getters seamlessly during testing.
The Role of Python’s Descriptor Protocol
To understand PropertyMock, you must first understand
Python descriptors. A descriptor is an object attribute with "binding
behavior," meaning its attribute access is overridden by methods in the
descriptor protocol: __get__(), __set__(), and
__delete__().
Built-in Python @property decorators are descriptors.
When you access instance.my_attribute, Python inspects the
class of instance. If it finds an object in the class
namespace that defines a __get__() method, Python calls
that method instead of retrieving a standard value from the instance
dictionary:
# Behind the scenes:
type(instance).__dict__['my_attribute'].__get__(instance, type(instance))PropertyMock is a subclass of Mock that
implements this exact descriptor protocol.
How
PropertyMock Implements __get__
Under the hood, PropertyMock overrides the
__get__ method to intercept read requests. When an
attribute represented by a PropertyMock is accessed:
- Access Trigger: The code attempts to read an
attribute on an instance (e.g.,
obj.status). - Descriptor Delegation: Because Python locates the
PropertyMockon the class level, it invokesPropertyMock.__get__(self, obj, obj_type). - Execution as a Callable: Inside
__get__,PropertyMockcalls itself:return self(). - Value Resolution: Calling
self()triggers standardMockevaluation rules. It increments the mock's call count, records call arguments, checks for assignedreturn_valueorside_effectconfigurations, and returns the result to the caller.
Because __get__ invokes the mock automatically, the
caller receives the return value directly without having to add
parentheses () to invoke the property.
The Class vs. Instance Binding Requirement
Because descriptors rely on class-level lookup, Python only invokes
__get__ automatically when the descriptor exists on the
target object's class, not on the object's instance
dictionary.
If you attach a PropertyMock directly to an
instance:
from unittest.mock import Mock, PropertyMock
mock_instance = Mock()
mock_instance.status = PropertyMock(return_value="active")
print(mock_instance.status)The output will be the <PropertyMock ...> instance
itself, rather than the string "active". Python's attribute
lookup rules bypass __get__ when an attribute is stored
directly in an instance's __dict__.
To successfully intercept reads, PropertyMock must be
assigned to the class:
from unittest.mock import Mock, PropertyMock
# Attach to the class of the mock
mock_instance = Mock()
type(mock_instance).status = PropertyMock(return_value="active")
# Accessing the attribute triggers __get__()
print(mock_instance.status) # Outputs: 'active'When using helper utilities like
unittest.mock.patch.object, the patcher handles this
automatically under the hood by binding the PropertyMock to
the target object's class for the duration of the test:
from unittest.mock import patch
with patch.object(TargetClass, 'status', new_callable=PropertyMock) as mock_status:
mock_status.return_value = "active"
instance = TargetClass()
assert instance.status == "active"
mock_status.assert_called_once()Intercepting Writes
In addition to intercepting reads, PropertyMock
implements the descriptor's __set__ method. When an
assignment occurs (e.g., obj.status = "idle"),
PropertyMock.__set__(self, obj, val) executes, internally
calling self(val). This captures the assignment as a
function call with arguments, allowing assertions such as
mock_status.assert_called_with("idle").