pytest-mock icon indicating copy to clipboard operation
pytest-mock copied to clipboard

Spying on properties

Open blueyed opened this issue 8 years ago • 5 comments

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.

blueyed avatar Feb 29 '16 15:02 blueyed

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 avatar Feb 29 '16 21:02 nicoddemus

@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.

blueyed avatar Mar 01 '16 00:03 blueyed

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 :)

fogo avatar Mar 02 '16 12:03 fogo

Is there any progress?

fliiiix avatar Mar 23 '18 14:03 fliiiix

I don't think so, but I would be happy to review and merge a PR! 😁

nicoddemus avatar Mar 23 '18 14:03 nicoddemus