Some classes define their attributes (aka fields) at the class level (outside __init__
or any other function). Some classes define them inside their __init__
function or even from other functions. Some classes use both approaches.
class MyClass(object):
foo = 'foo'
def __init__(self, *args, **kwargs):
self.bar = 'bar'
The problem is, when you use dir
, it only includes 'bar'
if you pass in an instance of the class.
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo']
>>> myInstance = MyClass()
>>> dir(myInstance)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
(scroll to the far right to see the diff)
I have a situation where I need to avoid instantiating MyClass
but I want to use it as the spec
setting (to pass an isinstance
check) in a mock.patch
call in a unit test.
@mock.patch('mypackage.MyClass', spec=MyClass)
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
# uses 'thing' here, which uses MyClass.bar ...
Doing this causes:
AttributeError: Mock object has no attribute 'bar'
This makes sense because the mock docs says:
spec: This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError.
Even if I do instantiate MyClass
, I get a different error.
@mock.patch('mypackage.MyClass', spec=MyClass())
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
# uses 'thing' here, which uses MyClass.bar ...
Causes:
TypeError: 'NonCallableMagicMock' object is not callable
I don't really care about being strict with which functions/attributes I allow accessing of; I actually want the normal MagicMock behaviour which lets you call anything without AttributeError
. It seems like using spec
makes this strict even though I am just using spec
to pass isinstance
check.
How do I properly mock this class which is used in isinstance
checks and has attributes which are not defined at the class level?
Given that you just want to pass isinstance
checks, I think the simplest solution is to write a quick wrapper to mock.patch.object
that sets the __class__
attribute of the returned mock.
def my_patch(obj, attr_name):
fake_class = mock.MagicMock()
fake_instance = fake_class.return_value # calling a class returns an instance.
fake_instance.__class__ = getattr(obj, attr_name)
return mock.patch.object(obj, attr_name, fake_class)
It's used like mock.patch.object
:
@my_patch(some_module, 'MyClass')
def test_something(self, fake_my_class):
...
but the fake object should pass isinstance
checks the same way that a spec'd mock would.