How to mock a readonly property with mock?

PythonUnit TestingMockingPytest

Python Problem Overview


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.

Python Solutions


Solution 1 - Python

I think the better way is to mock the property as PropertyMock, rather than to mock the __get__ method directly.

It is stated in the documentation, search for unittest.mock.PropertyMock: A mock intended to be used as a property, or other descriptor, on a class. PropertyMock provides __get__ and __set__ methods so you can specify a return value when it is fetched.

Here is how:

class MyClass:
    @property
    def last_transaction(self):
	    # an expensive and complicated DB query here
	    pass

def test(unittest.TestCase):
    with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
	    mock_last_transaction.return_value = Transaction()
	    myclass = MyClass()
	    print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

Solution 2 - Python

Actually, the answer was (as usual) in the documentation, it's just that I was applying the patch to the instance instead of the class when I followed their example.

Here is how to do it:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

In the test suite:

def test():
    # Make sure you patch on MyClass, not on a MyClass instance, otherwise
    # you'll get an AttributeError, because mock is using settattr and
    # last_transaction is a readonly property so there's no setter.
    with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
        mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
        myclass = MyClass()
        print myclass.last_transaction

Solution 3 - Python

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

Solution 4 - Python

Probably a matter of style but in case you prefer decorators in tests, @jamescastlefield's answer could be changed to something like this:

class MyClass:
    @property
    def last_transaction(self):
	    # an expensive and complicated DB query here
	    pass

class Test(unittest.TestCase):
    @mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
    def test(self, mock_last_transaction):
	    mock_last_transaction.return_value = Transaction()
		myclass = MyClass()
		print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

Solution 5 - Python

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

Solution 6 - Python

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

Solution 7 - Python

If you don't want to test whether or not the mocked property was accessed you can simply patch it with the expected return_value.

with mock.patch(MyClass, 'last_transaction', Transaction()):
    ...

Solution 8 - Python

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.

Solution 9 - Python

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncharlaxView Question on Stackoverflow
Solution 1 - PythonjamescastlefieldView Answer on Stackoverflow
Solution 2 - PythoncharlaxView Answer on Stackoverflow
Solution 3 - PythonTim SwastView Answer on Stackoverflow
Solution 4 - PythonEyal LevinView Answer on Stackoverflow
Solution 5 - PythonlmiguelvargasfView Answer on Stackoverflow
Solution 6 - PythonConchylicultorView Answer on Stackoverflow
Solution 7 - PythonSimon CharetteView Answer on Stackoverflow
Solution 8 - Pythonmike rodentView Answer on Stackoverflow
Solution 9 - PythonMateus Alves de OliveiraView Answer on Stackoverflow