pythonpython-3.xpython-unittestpython-unittest.mock

Patching a property of a used class


I'm trying to patch two properties from a class but the mocking is returning a MagicMock instead the expected return value (String).

Client class:

class ClientApi:
    def create_path(self):
        return f"client_api_{self.time_now}T{self.date_now}.json"

    @property
    def date_now(self):
        return datetime.now().strftime("%Y-%m-%d")

    @property
    def time_now(self):
        return datetime.now().strftime("%H:%M:%S")    

Test Class

class TestClientApi:

    @pytest.fixture
    def setup_class(self):
        yield ClientApi()

    def test_create_path(self, setup_class):
        with patch("client_api.ClientApi.date_now") as mock_date_now, \
                patch(client_api.ClientApi.time_now") as mock_time_now:

            mock_date_now.return_value = "2023-11-28"
            mock_time_now.return_value = "00:00:00"
            expected_path = "client_api_2023-11-28T00:00:00.json"
            assert expected_path = setup_class.create_path()

The create_path method is returning "<MagicMock name='time_now' id='1687227445600'>T<MagicMock name='date_now' id='1687227461936'>.json" and I would like to understand why.

I would like to ask as well if anyone know how can I compress the two patches in one and if it will work, for example:

def test_create_path(self, setup_class):
    with patch("client_api.ClientApi") as mock_client_api:

        mock_client_api.return_value.date_now.return_value = "2023-11-28"
        mock_client_api.return_value.time_now.return_value = "00:00:00"

Or any similar way, but it's not mocking it and just sending the current timestamp.

Thank you in advance


Solution

  • When you make an instance method a property it becomes a descriptor instance and is no longer a callable, so you cannot patch the descriptor instance with a mock object and still expect it to behave like a descriptor and automatically call its underlying getter function for you.

    Since a property behaves like an instance attribute, you can simply use patch.multiple instead to replace the two properties with the mock values you want:

    with patch.multiple(ClientApi, date_now="2023-11-28", time_now="00:00:00"):
        expected_path = "client_api_2023-11-28T00:00:00.json"
        assert expected_path = setup_class.create_path()