简体   繁体   中英

How to unit test a decorated method

Assume I have to unit test methodA , defined in the following class:

class SomeClass(object):

    def wrapper(fun):
        def _fun(self, *args, **kwargs):
            self.b = 'Original'
            fun(self, *args, **kwargs)
        return _fun

    @wrapper
    def methodA(self):
        pass

My test class is as follows:

from mock import patch

class TestSomeClass(object):

    def testMethodA(self):
        def mockDecorator(f):
            def _f(self, *args, **kwargs):
                self.b = 'Mocked'
                f(self, *args, **kwargs)
            return _f

        with patch('some_class.SomeClass.wrapper', mockDecorator):
            from some_class import SomeClass
            s = SomeClass()
            s.methodA()
            assert s.b == 'Mocked', 's.b is equal to %s' % s.b

If I run the test, I hit the assertion:

File "/home/klinden/workinprogress/mockdecorators/test_some_class.py", line 17, in testMethodA
    assert s.b == 'Mocked', 's.b is equal to %s' % s.b
AssertionError: s.b is equal to Original

If I stick a breakpoint in the test, after patching, this is I can see wrapper has been mocked out just fine, but that methodA still references the old wrapper:

(Pdb) p s.wrapper
<bound method SomeClass.mockDecorator of <some_class.SomeClass object at 0x7f9ed1bf60d0>>
(Pdb) p s.methodA
<bound method SomeClass._fun of <some_class.SomeClass object at 0x7f9ed1bf60d0>>

Any idea of what the problem is here?

After mulling over, I've found a solution.

Since monkey patching seems not to be effective (and I've also tried a few other solutions), I dug into the function internals and that proved to be fruitful.

Python 3 You're lucky - just use the wraps decorator, which creates a __wrapped__ attribute, which in turn contains the wrapped function. See the linked answers above for more details.

Python 2 Even if you use @wraps , no fancy attribute is created. However, you just need to realise that the wrapper method does nothing but a closure: so you'll be able to find your wrapped function in its func_closure attribute.

In the original example, the wrapped function would be at: s.methodA.im_func.func_closure[0].cell_contents

Wrapping up (ha!) I created a getWrappedFunction helper along this lines, to ease my testing:

@staticmethod
def getWrappedFunction(wrapper):
    return wrapper.im_func.func_closure[0].cell_contents

YMMV, especially if you do fancy stuff and include other objects in the closure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM