New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spying on properties #35

Open
blueyed opened this Issue Feb 29, 2016 · 5 comments

Comments

Projects
None yet
4 participants
@blueyed
Contributor

blueyed commented Feb 29, 2016

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.

@nicoddemus

This comment has been minimized.

Member

nicoddemus commented Feb 29, 2016

I'm not sure @blueyed... I think it would certainly be an useful feature. Do you have some sample code to share?

cc @fogo

@blueyed

This comment has been minimized.

Contributor

blueyed commented Mar 1, 2016

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

@fogo

This comment has been minimized.

Contributor

fogo commented Mar 2, 2016

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

@fliiiix

This comment has been minimized.

fliiiix commented Mar 23, 2018

Is there any progress?

@nicoddemus

This comment has been minimized.

Member

nicoddemus commented Mar 23, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment