Search code examples
pythonunit-testingpython-2.7mockingpython-decorators

Python unites - gathering multiple @patch decorators within another decorator


I'm writing an internal unit tests framework which involves mocking multiple functions/classes.
@patch seems to be working great for my needs, but as there are many tests that will require to patch many different classes/functions, I'm looking to avoid writing multiple @patch before every test and maybe encapsulate them all into another decorator. To better illustrate my needs:

current state:

@patch('p.A', mockedA)
@patch('p.B', mockedB)
.
.
@patch('p.N', mockedN)
def test_this()

desired state:

@patch_all
def test_this()

Is it possible to implement something like this?, so far I wasn't able to as @patch requires to be followed either def or another @.

EDIT 2:
I've tried Michele's suggestion, but the test is no longer being identified as a test:
After adding functools.wraps to the patch_all decorator, it worked.

def patch_all(f):
@patch('p.A', moduleA.classA.methodA)
@patch('p.B', moduleB.classB.methodB)
.
.
@patch('p.N', moduleN.classN.methodN)
wraps(f)
def functor(*args, **kwargs):
    return f(*args, **kwargs)
return functor


class TestWrapper(unittest.TestCase):
    @patch_all
    def my_test(self):
        my test goes here...

With the @patch_all decorator this is what I get:

nosetests Tester.py --nocapture 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

If I remove it:

$ nosetests Tester.py --nocapture 
.
----------------------------------------------------------------------
Ran 1 test in 7.692s

OK

Thanks in advance.


Solution

  • patch decorator like all decorators is just a function that take a function and return a function ([EDIT] in the original version I forgot @functools.wraps(f) to make a correct test decorator, thanks to @MenyIssakov to let me know that my answer was wrong). You can define your own patch_all decorator like

    def patch_all(f):
        @patch('p.A', argsA)
        @patch('p.B', argsB)
        .
        .
        @patch('p.N', argsN)
        @functools.wraps(f)
        def functor(*args, **kwargs):
            return f(*args, **kwargs)
        return functor
    

    Now you can use @patch_all decorator in your tests like:

    @patch_all
    def test_all(mockN, ..., mockB, mockA):
        my beautiful test
    

    You can go over this and define your own decorator that take a list of tuple to pass to the patch calls.

    However i think that is not a good idea: test should be simple and mocking should be explicit to make clear the aim of the test. If you must patch a lot of objects/methods/functions in a lot of tests functions consider to apply the decorator to the class instead of the single methods to write it just one time for all test methods.