22

I'm asking how to mock a class property in a unit test using Python 3. I've tried the following, which makes sense for me following the docs, but it doesn't work:

foo.py:

class Foo():
    @property
    def bar(self):
        return 'foobar'


def test_foo_bar(mocker):
    foo = Foo()
    mocker.patch.object(foo, 'bar', new_callable=mocker.PropertyMock)
    print(foo.bar)

I've installed pytest and pytest_mock and run the test like this:

pytest foo.py

I got the following error:

>       setattr(self.target, self.attribute, new_attr)
E       AttributeError: can't set attribute

/usr/lib/python3.5/unittest/mock.py:1312: AttributeError

My expectation would be that the test runs without errors.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
hek2mgl
  • 152,036
  • 28
  • 249
  • 266

2 Answers2

24

The property mechanism relies on the property attribute being defined on the object's class. You can't create a "property like" method or attribute on a single instance of a class (for a better understanding, read about Python's descriptor protocol)

Therefore you have to apply the patch to your class - you can use the with statement so that the class is properly restored after your test:

def test_foo_bar(mock):
    foo = Foo()
    with mock.patch(__name__ + "Foo.bar", new=mocker.PropertyMock)
        print(foo.bar)
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • That works! Also thanks for the link about the descriptor protocol. Indeed, I don't understand how properties are implemented under the hood so far. Will go through that. – hek2mgl Mar 10 '17 at 07:25
3

You can directly return the value if you do not need extra features

from mock import patch

@patch('foo.Foo.bar', 'mocked_property_value')
def test_foo_bar():
    foo = Foo()    
    print(foo.bar)

Or you can wrap MagicMocks with a call to function property:

from mock import patch, MagicMock

@patch('foo.Foo.bar', property(MagicMock(return_value='mocked_property_value')))
def test_foo_bar():
    foo = Foo()    
    print(foo.bar)
Stefan
  • 10,010
  • 7
  • 61
  • 117