Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 287
Meny Issakov Avatar asked Dec 24 '14 12:12

Meny Issakov


1 Answers

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.

like image 128
Michele d'Amico Avatar answered Nov 11 '22 02:11

Michele d'Amico