2

I have a function that runs some code if the object has a certain attribute, and in rare cases if if the object does not have the attribute, it runs different code. It is hard for me to create the object without the attribute for testing. I tried del instance.attribute but got an error. The attribute is actually a @property under the hood.

I have an object instance that has foo attribute. How does one mock it so that when one tries to access instance.foo it raises an AttributeError as usual if there is no attribute?

I tried mock.Mock(side_effect=AttributeError('Boom!')) but it only works with methods.

run_the_race
  • 1,344
  • 2
  • 36
  • 62
  • 1
    Use a [PropertyMock](https://docs.python.org/3/library/unittest.mock.html?highlight=patch#unittest.mock.PropertyMock) instead of a `Mock`. – MrBean Bremen Jul 01 '22 at 19:57
  • @MrBeanBremen Thats great for properties in general. The documentation has `mock_foo.return_value = 'mockity-mock'`, thats great for a static value, not sure how one would use it with a callable to raise an `AttributeError` – run_the_race Jul 02 '22 at 17:17
  • 1
    I'm adding a simple example as an answer (too inconvenient in a comment). Not sure if that is sufficient for your need - if not, you may want to adapt your question with some example code to show your use case. – MrBean Bremen Jul 02 '22 at 17:22

1 Answers1

3

You could try to use a PropertyMock for the property, and generally you shall be able to set the respective side effect. Here is a simple working example:

from unittest import mock
import pytest

class Foo:
    @property
    def bar(self):
        return "bar"


def test_no_foo():
    bar_mock = mock.PropertyMock()
    with mock.patch(f"{__name__}.Foo.bar", bar_mock):
        bar_mock.side_effect = AttributeError('Boom!')
        foo = Foo()
        with pytest.raises(AttributeError):
            foo.bar

As you patch the property in the class, not in the object, you can can also do this using patch.object if you have access to the object by accessing the class of the object:

def test_no_foo():
    bar_mock = mock.PropertyMock()
    foo = Foo()
    with mock.patch.object(foo.__class__, "bar", bar_mock):
        bar_mock.side_effect = AttributeError('Boom!')
        with pytest.raises(AttributeError):
            foo.bar
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Thanks for figuring that out, unfortunately the fotted path to the object will be different for different ppl (its not a constant), the class is a pluggable/swappble instance. I tried `with mock.patch.object(foo, 'bar'):` but it just says `AttributeError: can't set attribute` – run_the_race Jul 04 '22 at 07:28
  • 1
    You can try to patch the property via the class - I added another example in the answer. – MrBean Bremen Jul 04 '22 at 17:34
  • Great thanks so much, I missed that important point! – run_the_race Jul 05 '22 at 09:49