How do I directly mock a superclass with python mock?

前端 未结 2 1578
孤独总比滥情好
孤独总比滥情好 2020-12-30 02:23

I am using the python mock framework for testing (http://www.voidspace.org.uk/python/mock/) and I want to mock out a superclass and focus on testing the subclasses\' added b

2条回答
  •  天涯浪人
    2020-12-30 03:12

    I played around with mocking out super() as suggested by kindall. Unfortunately, after a great deal of effort it became quite complicated to handle complex inheritance cases.

    After some work I realized that super() accesses the __dict__ of classes directly when resolving attributes through the MRO (it does not do a getattr type of call). The solution is to extend a mock.MagicMock() object and wrap it with a class to accomplish this. The wrapped class can then be placed in the __bases__ variable of a subclass.

    The wrapped object reflects all defined attributes of the target class to the __dict__ of the wrapping class so that super() calls resolve to the properly patched in attributes within the internal MagicMock().

    The following code is the solution that I have found to work thus far. Note that I actually implement this within a context handler. Also, care has to be taken to patch in the proper namespaces if importing from other modules.

    This is a simple example illustrating the approach:

    from mock import MagicMock
    import inspect 
    
    
    class _WrappedMagicMock(MagicMock):
        def __init__(self, *args, **kwds):
            object.__setattr__(self, '_mockclass_wrapper', None)
            super(_WrappedMagicMock, self).__init__(*args, **kwds)
    
        def wrap(self, cls):
            # get defined attribtues of spec class that need to be preset
            base_attrs = dir(type('Dummy', (object,), {}))
            attrs = inspect.getmembers(self._spec_class)
            new_attrs = [a[0] for a in attrs if a[0] not in base_attrs]
    
            # pre set mocks for attributes in the target mock class
            for name in new_attrs:
                setattr(cls, name, getattr(self, name))
    
            # eat up any attempts to initialize the target mock class
            setattr(cls, '__init__', lambda *args, **kwds: None)
    
            object.__setattr__(self, '_mockclass_wrapper', cls)
    
        def unwrap(self):
            object.__setattr__(self, '_mockclass_wrapper', None)
    
        def __setattr__(self, name, value):
            super(_WrappedMagicMock, self).__setattr__(name, value)
    
            # be sure to reflect to changes wrapper class if activated
            if self._mockclass_wrapper is not None:
                setattr(self._mockclass_wrapper, name, value)
    
        def _get_child_mock(self, **kwds):
            # when created children mocks need only be MagicMocks
            return MagicMock(**kwds)
    
    
    class A(object):
        x = 1
    
        def __init__(self, value):
            self.value = value
    
        def get_value_direct(self):
            return self.value
    
        def get_value_indirect(self):
            return self.value
    
    
    class B(A):
        def __init__(self, value):
            super(B, self).__init__(value)
    
        def f(self):
            return 2
    
        def get_value_direct(self):
            return A.get_value_direct(self)
    
        def get_value_indirect(self):
            return super(B, self).get_value_indirect()
    
    # nominal behavior
    b = B(3)
    assert b.get_value_direct() == 3
    assert b.get_value_indirect() == 3
    assert b.f() == 2
    assert b.x == 1
    
    # using mock class
    MockClass = type('MockClassWrapper', (), {})
    mock = _WrappedMagicMock(A)
    mock.wrap(MockClass)
    
    # patch the mock in
    B.__bases__ = (MockClass, )
    A = MockClass
    
    # set values within the mock
    mock.x = 0
    mock.get_value_direct.return_value = 0
    mock.get_value_indirect.return_value = 0
    
    # mocked behavior
    b = B(7)
    assert b.get_value_direct() == 0
    assert b.get_value_indirect() == 0
    assert b.f() == 2
    assert b.x == 0
    

提交回复
热议问题