6

Please help me understand why the following doesn't work. In particular - instance attributes of a tested class are not visible to Python's unittest.Mock.

In the example below bar instance attribute is not accessible. The error returned is:

AttributeError: <class 'temp.Foo'> does not have the attribute 'bar'
import unittest
from unittest.mock import patch

class Foo:
    def __init__(self):
        super().__init__(self)
        self.bar = some_external_function_returning_list()

    def do_someting(self):
        calculate(self.bar)

class TestFoo(unittest.TestCase):

    @patch('temp.Foo.bar')
    def test_do_something(self, patched_bar):
        patched_bar.return_value = ['list_elem1', 'list_elem2']
kynleborg
  • 63
  • 1
  • 1
  • 6
  • `Foo.bar` (a class attribute) and `Foo().bar` (an instance attribute) are two different things. There is no existing class attribute to patch. – chepner Mar 03 '20 at 21:34
  • @chepner I understand that very well, that's why I'm asking about instance attributes only. – kynleborg Mar 03 '20 at 21:43
  • 1
    Without knowing what exactly you want to test, I wouldn't recommend patching anything. Whatever instance `f` of `Foo` you are creating for your test, you can simply change the attribute value with `f.bar = "mocked"`. – chepner Mar 03 '20 at 21:43
  • @chepner I've edited the post a bit. I do understand I could use your technique or mock `some_external_function_returning_list()`, but I don't get why I cannot do it the way I'm attempting to. Very similar approach is available for class attributes, described for example here: https://stackoverflow.com/questions/22324628/better-way-to-mock-class-attribute-in-python-unit-test – kynleborg Mar 03 '20 at 21:51
  • A class attribute simply has nothing to do with an instance attribute of the same name. The class doesn't "declare" an instance attribute. In the short interval of time between an instance of `Foo` being created by `Foo.__new__` and it being initialized by `Foo.__init__`, the instance attribute `bar` simply doesn't exist. Only when `__init__` executes the assignment `self.bar = ... ` does the attribute come into being for that instance. – chepner Mar 03 '20 at 21:54
  • And you haven't yet created an *instance* of `Foo` for your test. – chepner Mar 03 '20 at 21:55
  • I do understand, so it's only the matter of visibility? If an instance attribute is declared with `@property` decorator then `mock` might be useful? – kynleborg Mar 03 '20 at 21:57
  • @chepner I'm simplifying the example, thus maybe I haven't added something required for a real world test. That won't change the original error returned. – kynleborg Mar 03 '20 at 21:59
  • 1
    `property` doesn't create an instance attribute; it creates, well, a property, which is a special kind of *class* attribute that provides a callback for when it it accessed via an instance. Given your definition of `Foo`, I suspect that what you want to patch is `some_external_function_returning_list` for the duration of the call to `Foo.__init__`. – chepner Mar 03 '20 at 21:59
  • Thank you for clarification. I understand the dynamic nature of Python and that the solution (simple assignment to an instance variable, mocking `some_external...`) depends on a given scenario. – kynleborg Mar 03 '20 at 22:04
  • Please consider sharing your explanation as an answer so I can accept it. I can imagine people like me searching for 'mocking instance attributes' and this thread could be helpful. – kynleborg Mar 03 '20 at 22:09

1 Answers1

11

Patching is used to modify name or attribute lookup. In this case, there is no bar attribute of the class temp.Foo.

If the intent is to patch the instance variable, you either need an existing instance to modify

def test(self):
    f = Foo()
    with patch.object(f, 'bar', 3):
        self.assertEqual(f.bar, 3)

or you may want to patch the function call that initializes the instance attribute in the first place.

def test(self):
    with patch('some_external_function_returning_list', return_value=3):
        f = Foo()
    self.assertEqual(f.bar, 3)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • What would be the way to go ahead and patch multiple instance attributes like you do in the second example with function initialization? – Adventure-Knorrig Jun 14 '23 at 08:32