pytest-mock
pytest-mock copied to clipboard
Spying on properties
I've tried to use spy
on object properties, but it fails with AttributeError: can't set attribute
in unittest/mock.py
.
I've started looking into adding support for (through PropertyMock
), but failed to make it work without accessing/calling the property method during setup of the spy - it would be better to only call it when it's accessed from the test.
I would appreciate any feedback / help in this regard.
I'm not sure @blueyed... I think it would certainly be an useful feature. Do you have some sample code to share?
cc @fogo
@nicoddemus
My idea was to first patch the class to allow setting a new attribute (using PropertyMock
) and then patch the attribute itself.
But it felt like having to patch/use fget
/fset
from the property
class itself.
This a nice feature that I didn't even think about because I guess never really mocked a property before.
Below it is a draft I've come up in a hurry that is really buggy but get the basics done:
def spy(self, obj, name):
if isinstance(getattr(obj, name), property):
prop = getattr(obj, name)
class SpyPropertyMock(mock_module.PropertyMock):
def __get__(self, obj, obj_type):
self()
return prop.__get__(obj)
def __set__(self, obj, val):
self(val)
prop.fset(obj, val)
result = self.patch.object(obj, name, new_callable=SpyPropertyMock)
return result
else:
method = getattr(obj, name)
autospec = inspect.ismethod(method) or inspect.isfunction(method)
# Can't use autospec classmethod or staticmethod objects
# see: https://bugs.python.org/issue23078
if inspect.isclass(obj):
# bypass class descriptor:
# http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
value = obj.__getattribute__(obj, name)
if isinstance(value, (classmethod, staticmethod)):
autospec = False
result = self.patch.object(obj, name, side_effect=method,
autospec=autospec)
return result
A test using it:
def testFoo(mocker):
mocked = mocker.spy(Foo, 'foo')
foo = Foo()
foo.foo = 'b'
assert foo.foo == 'b'
assert foo.foo != 'c'
assert foo.foo != 'd'
print mocked.mock_calls # [call(u'b'), call(), call(), call()]
With this, the mock object returned can be used to inspect mock_calls
to property and property value is in fact changed.
Problems I've seen so far:
- I'm unable to access
Foo.foo
without getting errors. - Different from method spies, object accessed by
foo
attr isn't a mock object, so it is only possible to access mock call objects through returned object.
Anyway I have to get more used to PropertyMock
before moving on, let me know more details about your problems and implementation (maybe open a PR?) because I can try to help :)
Is there any progress?
I don't think so, but I would be happy to review and merge a PR! 😁