Can one access the `self` object while mocking a property with PropertyMock using a side_effect function?

53 views Asked by At

I am trying to mock a property and would like to control the return value of the property according to other state in the object. A small representative example looks like this

import datetime
from pytest_mock import MockFixture


class MyObject:
    def __init__(self, name: str):
        self._name = name
        self._creation_time = datetime.datetime.now()

    @property
    def creation_time(self) -> datetime.datetime:
        # A series of
        # calculations
        # here
        # return_value = ... something
        return return_value
        

def test_ordered_creation_time(mocker: MockFixture) -> None:

    def creation_time_side_effect(self) -> datetime.datetime:
        if self._name == 'first_created':
            return datetime.datetime(2020, 1, 1)
        elif self._name == 'second_created':
            return datetime.datetime(2020, 2, 1)

    mock_creation_time = mocker.patch.object(
        MyObject,
        'creation_time',
        new_callable=mocker.PropertyMock)
    mock_creation_time.side_effect = creation_time_side_effect

    first_created = MyObject('first_created')
    second_created = MyObject('second_created')

    assert first_created.creation_time < second_created.creation_time


Running this with pytest gives me


E  TypeError: test_ordered_creation_time.<locals>.creation_time_side_effect() missing 1 required positional argument: 'self'

It looks like while using PropertyMock and setting a side_effect function, that function does not have access to the self object. Is that correct ? If yes, why is that ?

Alternatively, is there another way to inspect the self object while mocking a property


Full error from pytest


====================================================================================================================================================================================== FAILURES ======================================================================================================================================================================================
_____________________________________________________________________________________________________________________________________________________________________________ test_ordered_creation_time _____________________________________________________________________________________________________________________________________________________________________________

mocker = <pytest_mock.plugin.MockerFixture object at 0x103fb1210>

    def test_ordered_creation_time(mocker: MockFixture) -> None:

        def creation_time_side_effect(self) -> datetime.datetime:
            if self._name == 'first_created':
                return datetime.datetime(2020, 1, 1)
            elif self._name == 'second_created':
                return datetime.datetime(2020, 2, 1)

        mock_creation_time = mocker.patch.object(
            MyObject,
            'creation_time',
            new_callable=mocker.PropertyMock)
        mock_creation_time.side_effect = creation_time_side_effect

        first_created = MyObject('first_created')
        second_created = MyObject('second_created')

>       assert first_created.creation_time < second_created.creation_time

dummy.py:38:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/homebrew/Cellar/[email protected]/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:2946: in __get__
    return self()
/opt/homebrew/Cellar/[email protected]/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1124: in __call__
    return self._mock_call(*args, **kwargs)
/opt/homebrew/Cellar/[email protected]/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1128: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <PropertyMock name='creation_time' id='4361753680'>, args = (), kwargs = {}, effect = <function test_ordered_creation_time.<locals>.creation_time_side_effect at 0x103f9e3e0>

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method

        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
                    raise result
            else:
>               result = effect(*args, **kwargs)
E               TypeError: test_ordered_creation_time.<locals>.creation_time_side_effect() missing 1 required positional argument: 'self'

/opt/homebrew/Cellar/[email protected]/3.11.4/Frameworks/Python.framework/Versions/3.11/lib/python3.11/unittest/mock.py:1189: TypeError
1

There are 1 answers

0
Danila Ganchar On

Just use PropertyMock + side_effect. Here is an example:

# run.py
from unittest.mock import PropertyMock, patch
from datetime import datetime

class MyObject:
    @property
    def creation_time(self) -> datetime:
        return datetime.utcnow()


def test_ordered_creation_time() -> None:
    with patch(
        # change run to your module...
        'run.MyObject.creation_time',
        new_callable=PropertyMock,
        side_effect=[datetime(2020, 1, 1), datetime(2020, 1, 2)]
    ):
        assert MyObject().creation_time < MyObject().creation_time

#============================= test session starts #==============================
#collecting ... collected 1 item
#
#run.py::test_ordered_creation_time PASSED                              [100%]
#
#============================== 1 passed in 2.61s #===============================