Search code examples
pythonunit-testingmockingpython-mock

Unable to patch an object's attribute correctly


I have a Python module as follows:

# src/exec.py

class A:

    def run(self, stuff):
        b = B(stuff.x)

class B:
    def __init__(self, x):
        self.obj = self.create_some_obj()

I'm trying to test a part of class A independently, for which I need to replace the obj in B with a fake object. I'm doing this as follows:

# test/test_execs.py

import exec as ex

class FakeObjForB:
    def __init__(self):
        # some init

class TestClass:

    @patch.object(ex.B, 'obj', FakeObjForB())
    def test_with_fake_obj(self):
        a = ex.A()
        a.run()
        # assert something about the state of a that depends on the b inside its run method

Running this test gives me the error: AttributeError: <class 'B'> does not have the attribute 'obj'. I tried replacing the line with the @patch decorator with @patch.object(ex.B, 'obj', FakeObjForB(), create=True). This, however, results in b.obj using the actual definition, and not FakeObjForB, which in turn leads to a false-failure in the assertion in test_with_fake_obj. Any clues about what I'm doing incorrectly here?


Solution

  • In your example you're patching the B class, that's the object passed as the first argument. That class doesn't declare obj attribute on the class level and so AttributeError is raised. When you provide create=True it won't complain as that argument allows the obj attribute to be dynamically created when needed/accessed. But, that won't ever happen as the very first "access" of that attribute is its actual creation - no dynamic mocking ever happened.

    A solution is to actually patch the method whose returned value would be assigned to the obj attribute, like:

    @patch.object(ex.B, 'create_some_obj', FakeObjForB())