Search code examples
pythonmockingpytest

How to mock property with side effects based on self, using PyTest


I've tried the code bellow, using new_callable=PropertyMock to mock a property call, and autospec=True to be able to access self in the side effect function:

from unittest.mock import PropertyMock

def some_property_mock(self):
    if self.__some_member == "some_value"
        return "some_different_value"
    else:
        return "some_other_value"

mocker.patch.object(
    SomeClass, "some_property", new_callable=PropertyMock, autospec=True, side_effect=some_property_mock)

It throws the following exception: ValueError: Cannot use 'autospec' and 'new_callable' together

Is there any alternative to achieve the expected behavior?

Edit: I have tried the solution provided in this post https://stackoverflow.com/a/77940234/7217960 but it doesn't seem to work with PropertyMock. Printing result gives MyMock name='my_property()' id='136687332325264' instead of 2 as expected.

from unittest import mock

class MyClass(object):
    def __int__(self):
        self.my_attribute = 10

    @property
    def my_property(self):
        return self.my_attribute + 1
 
def unit_under_test():
    inst = MyClass()
    return inst.my_property
 
class MyMock(mock.PropertyMock):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("MyMock __init__ called.")
 
with mock.patch.object(mock, 'MagicMock', MyMock):
    with mock.patch.object(MyClass, 'my_property', autospec=True, side_effect=lambda self: 2) as spy:
        result = unit_under_test()
        assert result == 2
        assert spy.call_count == 1

Solution

  • Since the property some_property is patched with MagicMock, which is then patched with MyMock, and you want to set the return value of the property callable when it's called, you should do so on the mock object returned from patching MagicMock with MyMock instead:

    with mock.patch.object(mock, 'MagicMock', MyMock) as property_mock:
        with mock.patch.object(MyClass, 'my_property', autospec=True) as spy:
            property_mock.side_effect = lambda self: 2
            # or property_mock.return_value = 2
            result = unit_under_test()
            assert result == 2
            assert spy.call_count == 1
    

    Demo: https://ideone.com/rKo8mO