I have a base class extending unittest.TestCase, and I want to patch that base class, such that classes extending this base class will have the patches applied as well.
Code Example:
@patch("some.core.function", mocked_method)
class BaseTest(unittest.TestCase):
#methods
pass
class TestFunctions(BaseTest):
#methods
pass
Patching the TestFunctions
class directly works, but patching the BaseTest class does not change the functionality of some.core.function
in TestFunctions
.
You probably want a metaclass here: a metaclass simply defines how a class is created.
By default, all classes are created using Python's built-in class type
:
>>> class Foo:
... pass
...
>>> type(Foo)
<class 'type'>
>>> isinstance(Foo, type)
True
So classes are actually instances of type
.
Now, we can subclass type
to create a custom metaclass (a class that creates classes):
class PatchMeta(type):
"""A metaclass to patch all inherited classes."""
We need to control the creation of our classes, so we wanna override the type.__new__
here, and use the patch
decorator on all new instances:
class PatchMeta(type):
"""A metaclass to patch all inherited classes."""
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls = patch("some.core.function", mocked_method)(cls)
return cls
And now you simply set the metaclass using __metaclass__ = PatchMeta
:
class BaseTest(unittest.TestCase):
__metaclass__ = PatchMeta
# methods
The issue is this line:
cls = patch("some.core.function", mocked_method)(cls)
So currently we always decorate with arguments "some.core.function"
and mocked_method
.
Instead you could make it so that it uses the class's attributes, like so:
cls = patch(*cls.patch_args)(cls)
And then add patch_args
to your classes:
class BaseTest(unittest.TestCase):
__metaclass__ = PatchMeta
patch_args = ("some.core.function", mocked_method)
Edit: As @mgilson mentioned in the comments, patch()
modifies the class's methods in place, instead of returning a new class. Because of this, we can replace the __new__
with this __init__
:
class PatchMeta(type):
"""A metaclass to patch all inherited classes."""
def __init__(cls, *args, **kwargs):
super(PatchMeta, self).__init__(*args, **kwargs)
patch(*cls.patch_args)(cls)
Which is quite unarguably cleaner.
Generally, I prefer to do this sort of thing in setUp
. You can make sure that the patch gets cleaned up after the test is completed by making use of the tearDown
method (or alternatively, registering a the patch's stop
method with addCleanup
):
class BaseTest(unittest.TestCase):
def setUp(self):
super(BaseTest, self).setUp()
my_patch = patch("some.core.function", mocked_method)
my_patch.start()
self.addCleanup(my_patch.stop)
class TestFunctions(BaseTest):
#methods
pass
Provided that you're disciplined enough to always call super
in your overridden setUp
methods, it should work just fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With