buzzfeed / caliendo

caliendo
MIT License
16 stars 5 forks source link

class-level @patch decorator #83

Open gerardo-orozco opened 10 years ago

gerardo-orozco commented 10 years ago

Given you have a particular feature in foo_bar.py that you need to test, you will write a unittest.TestCase subclass and several methods that test the different use cases your feature may have, but you want to patch, say, foo_bar.Foo.foo and foo_bar.Bar.bar for all the use cases. Under this scenario you will probably find yourself repeating @patch decorators over the diverse test methods you wrote.

Avoid repeating patch

It would be nice to have @patch also work as a class decorator so that you can decorate the test class and have @patch stub out the given callables for ALL the test methods:

@patch('foo_bar.Foo.foo')
@patch('foo_bar.Bar.bar')
class FooBarTestCase(unittest.TestCase):
    ...

instead of

class FooBarTestCase(unittest.TestCase):
    @patch('foo_bar.Foo.foo')
    @patch('foo_bar.Bar.bar')
    def test_foo(self):
        ...

    @patch('foo_bar.Foo.foo')
    @patch('foo_bar.Bar.bar')
    def test_bar(self):
        ...

@patch overriding

BUT you may also find that there are exceptions to the rule where you want the class-level @patch not to be applied for one or more particular test methods in the class because you want to override it with an alternative rvalue or side_effect. @patch should be able receive an optional argument ignore_tests=[] in which case the patch should be ignored when the given tests are running:

@patch('foo_bar.Foo.foo')
@patch('foo_bar.Bar.bar', ignore_tests=['test_bar'])
class FooBarTestCase(unittest.TestCase):
    def test_foo(self):
        ...

    @patch('foo_bar.Bar.bar', rvalue='bar')
    def test_bar(self):
        ...