How does unittest.mock.PropertyMock fake an attribute that is backed by a property?
answer
- Attribute access becomes a recorded call
- Descriptors are honoured on the type only
- Reading records a no-argument call
- Assigning records a one-argument call
- patch.object plus new_callable does the wiring
basics
~20 sPropertyMock implements the descriptor hooks, so it only works when attached to the class rather than to an instance. Reading the attribute calls it with no arguments; assigning to it calls it with the new value.
solid answer
~40 sA `property` is a descriptor living on the class, so replacing it with an ordinary `Mock` attribute on the instance does not work — the class descriptor still wins on read, and a read-only property rejects the assignment outright. `unittest.mock.PropertyMock` is a `Mock` subclass that implements `__get__` and `__set__`, so it can *be* the class attribute. Attach it with `patch.object(Cls, "name", new_callable=PropertyMock)`, which installs it on the class and restores the real property afterwards. Every read records a zero-argument call, so `p.assert_called_once_with()` verifies the attribute was read; every assignment records a one-argument call carrying the value. Keep your own reference to the mock: reading `Cls.name` goes through `__get__` too and hands you the configured value, not the mock. Set `p.side_effect` to make the attribute raise.
code
python · 14 linesfrom unittest.mock import PropertyMock, patch
class IngestConfig:
@property
def source_dir(self):
raise OSError("reads the real mount")
cfg = IngestConfig()
with patch.object(IngestConfig, "source_dir", new_callable=PropertyMock) as p:
p.return_value = "/var/log/staged"
assert cfg.source_dir == "/var/log/staged"
p.assert_called_once_with() # a read is a no-argument callgo deeper
Know that a property cannot be faked by assigning to the instance, and that unittest.mock.PropertyMock plus patch.object on the class is the standard recipe when a test must not run the real property body.
Explain the descriptor mechanics: PropertyMock defines get and set, so it must live on the type, a read is a no-argument call and an assignment is a one-argument call.
Show the operational care. A class-level patch is process-wide state, so restoration matters; use side_effect for per-read values or to make the attribute raise the failure the handler was written for.
Question the need. Frequent property doubling usually signals a class doing I/O behind attribute access; weigh injecting the value at construction against normalising a patching pattern the whole suite must then maintain.
`PropertyMock` exists because attribute *access* is not a method call from the caller's point of view, and a normal mock can only intercept calls. ## Why an ordinary mock attribute is not enough `property` is a descriptor: an object defining `__get__` (and optionally `__set__`) that lives on the class. When you evaluate `obj.attr`, the interpreter checks `type(obj)` first, and a data descriptor — one defining `__set__` or `__delete__`, which `property` does — takes precedence over anything in the instance `__dict__`. So `obj.attr = Mock()` on a real object with a read-only property raises `AttributeError: property 'attr' of 'C' object has no setter`, and even where the assignment succeeds the class descriptor still governs the read. There is a second, subtler failure. If you set a plain `Mock` in place of a property that the code reads as a value, the code gets the mock object itself rather than a value, and comparisons, string formatting or arithmetic against it behave in surprising ways instead of failing loudly. ## What PropertyMock is `PropertyMock` is a subclass of `Mock` that defines `__get__` and `__set__`. Those hooks turn attribute traffic into calls on the mock: * reading the attribute calls the mock with **no** arguments and returns its `return_value`; * assigning to the attribute calls the mock with **one** argument, the assigned value. That is why `p.assert_called_once_with()` — empty parentheses — is the assertion for "the attribute was read once", and why an assignment shows up in `call_args_list` as `call("new value")`. It is also why `PropertyMock` must be attached to a **class**. Descriptor hooks are only honoured on the type; assigning `instance.attr = PropertyMock(...)` merely stores the mock object in the instance dictionary, and reading it back gives you a `PropertyMock` instance, not `"the value"`. ## Attaching it The supported spelling is to let `patch` do it: ```python with patch.object(IngestConfig, "source_dir", new_callable=PropertyMock) as p: p.return_value = "/var/log/staged" ``` `new_callable` tells `patch` which class to instantiate for the replacement, and because `patch.object` targets the class, the mock lands where a descriptor is honoured. Restoration on exit is automatic, which matters more here than usual: you have mutated a class, and every other test in the process shares that class. You can also attach one by hand — `type(obj).attr = PropertyMock(return_value=...)` — including on a `MagicMock`, where `type(m)` is a per-instance subclass so the patch does not bleed into other mocks. Keep the reference you assigned, because reading `type(obj).attr` back triggers `__get__` and returns the configured value rather than the mock, leaving you with no handle for assertions. Hand-attaching also means hand-removing with `del`. ## What it is good for The natural use is a property whose real implementation does something a test must not do — reads a mount point, opens a connection, consults a clock or a config file. Doubling the property keeps the object under test otherwise real. It is equally good for the negative case: `p.side_effect = OSError("mount unavailable")` makes the attribute *raise* on access, which is an awkward failure to provoke any other way and often the one the error-handling branch was written for. It also fits attributes that are not properties in production but whose access you want to count — a mock collaborator whose `encoding` or `closed` attribute the code reads, where the assertion is about *whether it was consulted*, not about the value. ## The gotchas worth naming Reading through the class rather than an instance still fires `__get__`, so `Cls.attr` gives a value and not the mock. A `PropertyMock` attached to a class is shared by every instance of it, so per-instance values need `side_effect` rather than `return_value`. And the recorded call for a read carries no arguments at all, which trips people who expect `assert_called_once_with(instance)` in the style of a descriptor's real `__get__` signature — the mock deliberately hides the descriptor plumbing and records the access, not the protocol.
- How do you assert that the code under test read the attribute rather than checking its value?A read is recorded as a zero-argument call on the `PropertyMock`, so `p.assert_called_once_with()` proves it was read exactly once, and `p.call_count` counts repeated reads — useful when the point is that an expensive property is consulted once and cached. An assignment is recorded as a one-argument call, so `p.assert_called_once_with("new value")` covers the write side.
- How would you make the attribute raise an exception when the code touches it?Set `side_effect` on the `PropertyMock`: `p.side_effect = OSError("mount unavailable")`. Because the read is implemented as a call, the exception is raised at the point of attribute access in the code under test. That is the cleanest way to exercise error handling around a property that fails only in conditions you cannot reproduce locally.
- Two instances of the same class need different values for the patched property. How?A `PropertyMock` sits on the class, so `return_value` is necessarily shared by every instance. Give it a `side_effect` callable that returns successive values — it is invoked once per read, so a list-valued `side_effect` yields a different value per access — or restructure the test so each object is exercised under its own `patch.object` block.
A property is a doorbell wired into the wall of the house, not a gadget the visitor carries. To fake it you must rewire the wall — the class — and then every ring is logged.
saying these in an interview costs you the question
- Assigns the PropertyMock to the instance instead of the class
- Expects a read to record a call carrying the instance
- Thinks a plain Mock attribute can replace a property
- Forgets a class-level patch leaks into other tests
- Reads the mock back through the class and gets a value
- Assumes return_value can differ per instance