andrea_crotti
andrea_crotti

Reputation: 3100

patching all methods in a subclass in Python

I have my nice base test class which extends from the django test case and another class:

 class MyTestCase(TestCase, TestFreshProvisionedEachTest):

Now it all works nicely, except that to make it work we have to patch (with Foord's mock library) a couple of functions in the middleware.

My idea is that this would have worked fine:

@patch('spotlight.middleware.extract_host_name', new=lambda host_name:
TEST_DOMAIN)
@patch('spotlight.middleware.extract_host_key', new=lambda host_key:
company_name_to_db(TEST_DOMAIN))
class MyTestCase(TestCase, TestFreshProvisionedEachTest):

However it doesn't seem to work, the methods of the subclass are not patched. I thought then that there could be a way to do something like

MyTestCase = patch(MyTestCase, 'spotlight.middleware.extract_host_name', new=lambda host_name: TEST_DOMAIN)

But that also is not possible.

Is there a way to avoid this repetition and do a patch on a superclass which patches all the subclasses methods as well?

Upvotes: 2

Views: 738

Answers (1)

1st1
1st1

Reputation: 1101

It's better to use a metaclass for that, as it makes easier to handle inheritance of test cases, than manually applying decorators. Something along these lines (I haven't tested it, but you should get the idea):

class TestMeta(type(TestCase)):
    def patch_method(cls, meth):
        raise NotImplementedError

    def __new__(mcls, name, bases, dct):
        to_patch = []

        for methname, meth in dct.items():
            if methname.startswith('test') and callable(meth):
                to_patch.append(methname)

        cls = super().__new__(mcls, name, bases, dct)

        for methname in to_patch:
            meth = getattr(cls, methname)
            meth = cls.patch_method(meth)
            setattr(cls, methname, meth)

        return cls

class PatchedTestCase(TestCase, metaclass=TestMeta):
    @classmethod
    def patch_method(cls, meth):
        meth = patch('spotlight.middleware.extract_host_name', 
                     new=lambda host_name: TEST_DOMAIN)(meth)

        meth = patch('spotlight.middleware.extract_host_key', 
                     new=lambda host_key: company_name_to_db(TEST_DOMAIN))(meth)

        return meth

class MyTestCase(PatchedTestCase, TestFreshProvisionedEachTest):
    ...

With this approach all methods of all subclasses of PatchedTestCase will be patched. You can also define other base classes with different patch_method implementations.

Upvotes: 1

Related Questions