在具有动态属性的isinstance检查中使用的模拟类

时间:2015-03-11 20:49:43

标签: python python-2.7 python-unittest python-mock

某些类在类级别(__init__之外或任何其他函数)定义其属性(也称为字段)。有些类在__init__函数内或甚至从其他函数中定义它们。有些课程使用这两种方法。

class MyClass(object):
  foo = 'foo'
  def __init__(self, *args, **kwargs):
    self.bar = 'bar'

问题是,当您使用dir时,如果您传入了该类的实例,则它只包含'bar'

>>> 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']

(滚动到最右边看差异)

我遇到的情况是我需要避免实例化MyClass,但我想将其用作spec设置(通过isinstance检查)mock.patch打电话给单元测试。

@mock.patch('mypackage.MyClass', spec=MyClass)
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
  # uses 'thing' here, which uses MyClass.bar ...

这样做会导致:

  

AttributeError:Mock对象没有属性'bar'

这是有道理的,因为mock docs说:

  

spec:这可以是字符串列表,也可以是充当模拟对象规范的现有对象(类或实例)。 如果传入一个对象,则通过在对象上调用dir(不包括不受支持的魔术属性和方法)来形成字符串列表。访问不在此列表中的任何属性将引发AttributeError。

即使我实例化MyClass,我也会遇到不同的错误。

@mock.patch('mypackage.MyClass', spec=MyClass())
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
  # uses 'thing' here, which uses MyClass.bar ...

原因:

  

TypeError:'NonCallableMagicMock'对象不可调用

我真的不关心我允许访问哪些功能/属性;我实际上想要正常的MagicMock行为,它可以让你在没有AttributeError的情况下调用任何东西。即使我只是使用spec传递spec支票,似乎使用isinstance也会严格限制。

问题:

如何正确模拟isinstance检查中使用的类,并且具有未在类级别定义的属性?

1 个答案:

答案 0 :(得分:1)

鉴于您只想传递isinstance个检查,我认为最简单的解决方案是为mock.patch.object编写一个快速包装器,设置返回模拟的__class__属性。

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)

它像mock.patch.object一样使用:

@my_patch(some_module, 'MyClass')
def test_something(self, fake_my_class):
    ...

但假对象应该通过isinstance检查,就像spec'd mock一样。