Gautam
Gautam

Reputation: 4026

Making PropertyMock wrap an existing property - Python

I want to test that an instance variable is "set" to specific values (i.e. multiple times) when a Python method is called.

Replacing the instance variable with a PropertyMock allows me to view the mock_calls and verify what values the property was set to. However, PropertyMock doesn't behave like a normal variable. When you set a value on it and try to read it, you get back another Mock. Is there a way to receive the value back instead?

Here's a contrived example:

import time
from unittest.mock import PropertyMock, call


class Machine:

    def __init__(self):
        self.mode = "idle"

    def start(self):

        # Update mode
        self.mode = "running"

        # Do some work, e.g. drive a motor for 0.5 sec
        time.sleep(0.5)

        # Restore mode
        if self.mode == "running":         # Should always be True, but isn't when using PropertyMock
            self.mode = "idle"


class Test_Machine:

    def test(self):

        # Create a machine
        real_machine = Machine()

        # Mock the 'mode' property
        mocked_property = PropertyMock()
        type(real_machine).mode = mocked_property

        # Call the method to test
        real_machine.start()

        print(mocked_property.mock_calls)                         # [call('running'), call(), call().__eq__('running')]
        assert call("running") == mocked_property.mock_calls[0]   # Success
        assert call("idle") == mocked_property.mock_calls[-1]     # Fails here


Upvotes: 2

Views: 609

Answers (1)

MrBean Bremen
MrBean Bremen

Reputation: 16855

I'm sure there is a better way to do this, but if you are just interested in the calls of the property setter, and want the getter to behave as the original property, you could override PropertyMock to behave like that:

class MyPropertyMock(PropertyMock):
    def __init__(self, value=None):
        super().__init__()
        self.value = value

    def __get__(self, obj, obj_type):
        return self.value  # the mock will not register these calls

    def __set__(self, obj, val):
        self.value = val
        super().__set__(obj, val)  # ensure the mock behavior in the setter


class Test_Machine:
    def test(self):
        real_machine = Machine()
        mocked_property = MyPropertyMock(real_machine.value)
        Machine.mode = mocked_property

        real_machine.start()

        print(mocked_property.mock_calls)  # [call('running'), call('idle')]
        ...

Upvotes: 1

Related Questions