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?
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())