0

I have a unittest.mock.PropertyMock (docs) object created for unit testing purposes, on an attribute within an object.

I no longer have the reference to the PropertyMock made, as the PropertyMock was monkeypatched in during test setup.

How can I get access to the PropertyMock for assertions, given I have the object with the given property?

from typing import Any
from unittest.mock import PropertyMock

class Foo:
    @property
    def property_1(self) -> int:
        return 1

def make_obj() -> Any:
    """Make some object, not returning a reference to the PropertyMock made inside."""
    my_obj = Foo()
    type(my_obj).property_1 = PropertyMock(return_value=100)
    # NOTE: function doesn't return the PropertyMock, only the object
    return my_obj

def test_make_obj() -> None:
    made_obj = make_obj()

    # We can see the PropertyMock is in place and works
    assert made_obj.property_1 == 100

    # How can I assert the property was set with 9001?  NOTE: I don't have access
    # to the PropertyMock made above
    made_obj.property_1 = 9001
    type(made_obj).property_1.assert_called_once_with(9001)  # This fails
    # AttributeError: 'int' object has no attribute 'assert_called_once_with'

In other words:

  1. After monkeypatching in the PropertyMock
  2. Calling type(my_obj).property_1 returns 100
  3. I want it to return the PropertyMock used (for assertions)
Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119
  • Not sure I understand what you are doing - don't you mean to set the property on the instance, e.g. `made_obj().property_1 = 9001`? – MrBean Bremen Jul 16 '21 at 18:35
  • Hello @MrBeanBremen I have tweaked the sample code and wording of the question. Yes I can set the `PropertyMock`, but afterwards, how can I assert it was called with 9001? – Intrastellar Explorer Jul 19 '21 at 17:28

1 Answers1

1

This is a bit tricky, because accessing the property mock via the attribute will always return the result of the mock instead of the mock itself. This is due to the way it functions: a PropertyMock overwrites __get__ to return the set value, which will be used as soon as you use the attribute access via the dot notation (or if you use getattr). You cannot overwrite __get__ yourself, because that would destroy the functionality of the PropertyMock.

You can work around this by directly accessing the __dict__ of your class:

def test_make_obj() -> None:
    made_obj = make_obj()
    assert made_obj.property_1 == 100
    made_obj.property_1 = 9001
    assert made_obj.property_1 == 9001
    type(made_obj).__dict__["property_1"].assert_called_once_with(9001)

You can probably use some helper function to make this a bit nicer, but you still will need the access via the attribute name, at least I see no other way.

The only other possibility I can think of is to pass the mock itself, though that is of course somewhat redundant and ugly:

def make_obj() -> Any:
    my_obj = Foo()
    pmock = PropertyMock(return_value=100)
    type(my_obj).property_1 = pmock
    return my_obj, pmock

def test_make_obj() -> None:
    made_obj, pmock = make_obj()
    assert made_obj.property_1 == 100
    made_obj.property_1 = 9001
    assert made_obj.property_1 == 9001
    pmock.assert_called_once_with(9001)
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • What is `MyPropertyMock` in the second example? And yes, I see what you mean with the `__dict__`, I should have checked if the `PropertyMock` was accessible there. As far as the utility of the second example, in my actual use case, I need to mock out a few `property`, so it becomes even more redundant/ugly – Intrastellar Explorer Jul 19 '21 at 20:48
  • 1
    Sorry, `MyPropertyMock` was a leftover, missed that And yes, I added the second example more for the sake of completeness that could be used in a simple case. – MrBean Bremen Jul 20 '21 at 03:22