Mock class 用于具有动态属性的 isinstance 检查

Mock class used in isinstance checks which has dynamic attributes

一些 classes 在 class 级别(在 __init__ 或任何其他函数之外)定义它们的属性(又名字段)。一些 classes 在它们的 __init__ 函数中甚至从其他函数中定义它们。一些 class 使用这两种方法。

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

问题是,当您使用 dir 时,如果您传入 class.

的实例,它只包含 '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 但我想在 [=22] 中将其用作 spec 设置(以通过 isinstance 检查) =] 调用单元测试。

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

这样做会导致:

AttributeError: Mock object has no attribute 'bar'

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

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.

即使我实例化 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' object is not callable

我真的不在乎严格限制functions/attributes我允许访问的内容;我实际上想要正常的 MagicMock 行为,它可以让你在没有 AttributeError 的情况下调用任何东西。似乎使用 spec 使这个变得严格,即使我只是使用 spec 通过 isinstance 检查。

问题:

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

鉴于您只想通过 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 检查,就像规范模拟一样。