How to mock a readonly property with mock?

Viewed 96462

How do you mock a readonly property with mock?

I tried:

setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())

but the issue is that it then applies to all instances of the class... which breaks my tests.

Do you have any other idea? I don't want to mock the full object, only this specific property.

9 Answers

If the object whose property you want to override is a mock object, you don't have to use patch.

Instead, can create a PropertyMock and then override the property on the type of the mock. For example, to override mock_rows.pages property to return (mock_page, mock_page,):

mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages

In case you are using pytest along with pytest-mock, you can simplify your code and also avoid using the context manger, i.e., the with statement as follows:

def test_name(mocker): # mocker is a fixture included in pytest-mock
    mocked_property = mocker.patch(
        'MyClass.property_to_be_mocked',
        new_callable=mocker.PropertyMock,
        return_value='any desired value'
    )
    o = MyClass()

    print(o.property_to_be_mocked) # this will print: any desired value

    mocked_property.assert_called_once_with()

If you need your mocked @property to rely on the original __get__, you can create your custom MockProperty

class PropertyMock(mock.Mock):

    def __get__(self, obj, obj_type=None):
        return self(obj, obj_type)

Usage:

class A:

  @property
  def f(self):
    return 123


original_get = A.f.__get__

def new_get(self, obj_type=None):
  return f'mocked result: {original_get(self, obj_type)}'


with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
  mock_foo.side_effect = new_get
  print(A().f)  # mocked result: 123
  print(mock_foo.call_count)  # 1

I was directed to this question because I wanted to mock the Python version in a test. Not sure whether this is quite relevant to this question, but sys.version is obviously read-only (... though technically an "attribute" rather than a "property", I suppose).

So, after perusing this place and trying some stupidly complicated stuff I realised the answer was simplicity itself:

with mock.patch('sys.version', version_tried):
    if version_tried == '2.5.2':
        with pytest.raises(SystemExit):
            import core.__main__
        _, err = capsys.readouterr()
        assert 'FATAL' in err and 'too old' in err

... might help someone.

Another way for mock object properties is this:

import mock

mocked_object = mock.Mock()
mocked_object.some_property = "Property value"

print(mocked_object.some_property)
Related