Reputation: 2461
I have a unittest.mock.PropertyMock
(docs) object created for unit testing purposes, on an attribute within an object.
I no longer have the reference to the PropertyMock
made, as the PropertyMock
was monkeypatched in during test setup.
How can I get access to the PropertyMock
for assertions, given I have the object with the given property
?
from typing import Any
from unittest.mock import PropertyMock
class Foo:
@property
def property_1(self) -> int:
return 1
def make_obj() -> Any:
"""Make some object, not returning a reference to the PropertyMock made inside."""
my_obj = Foo()
type(my_obj).property_1 = PropertyMock(return_value=100)
# NOTE: function doesn't return the PropertyMock, only the object
return my_obj
def test_make_obj() -> None:
made_obj = make_obj()
# We can see the PropertyMock is in place and works
assert made_obj.property_1 == 100
# How can I assert the property was set with 9001? NOTE: I don't have access
# to the PropertyMock made above
made_obj.property_1 = 9001
type(made_obj).property_1.assert_called_once_with(9001) # This fails
# AttributeError: 'int' object has no attribute 'assert_called_once_with'
In other words:
PropertyMock
type(my_obj).property_1
returns 100
PropertyMock
used (for assertions)Upvotes: 0
Views: 887
Reputation: 16855
This is a bit tricky, because accessing the property mock via the attribute will always return the result of the mock instead of the mock itself.
This is due to the way it functions: a PropertyMock
overwrites __get__
to return the set value, which will be used as soon as you use the attribute access via the dot notation (or if you use getattr
). You cannot overwrite __get__
yourself, because that would destroy the functionality of the PropertyMock
.
You can work around this by directly accessing the __dict__
of your class:
def test_make_obj() -> None:
made_obj = make_obj()
assert made_obj.property_1 == 100
made_obj.property_1 = 9001
assert made_obj.property_1 == 9001
type(made_obj).__dict__["property_1"].assert_called_once_with(9001)
You can probably use some helper function to make this a bit nicer, but you still will need the access via the attribute name, at least I see no other way.
The only other possibility I can think of is to pass the mock itself, though that is of course somewhat redundant and ugly:
def make_obj() -> Any:
my_obj = Foo()
pmock = PropertyMock(return_value=100)
type(my_obj).property_1 = pmock
return my_obj, pmock
def test_make_obj() -> None:
made_obj, pmock = make_obj()
assert made_obj.property_1 == 100
made_obj.property_1 = 9001
assert made_obj.property_1 == 9001
pmock.assert_called_once_with(9001)
Upvotes: 2