I have a function with a decorator that I'm trying test with the help of the Python Mock library. I'd like to use mock.patch
to replace the real decorator with a mock 'bypass' decorator which just calls the function.
What I can't figure out is how to apply the patch before the real decorator wraps the function. I've tried a few different variations on the patch target and reordering the patch and import statements, but without success. Any ideas?
It works by decorating each test method in the class. This reduces the boilerplate code when your test methods share a common patchings set. patch finds tests by looking for method names that start with patch.
Python decorators are a powerful concept that allow you to "wrap" a function with another function. The idea of a decorator is to abstract away something that you want a function or class to do, besides its normal responsibility. This can be helpful for many reasons such as code reuse, and sticking to curlys law.
Python decorators can be easily tested by integration tests, but it is good practice to create some unit tests for them as well. You can easily unit test a decorator by applying it to a dummy function or mock.
Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.
It should be noted that several of the answers here will patch the decorator for the entire test session rather than a single test instance; which may be undesirable. Here's how to patch a decorator that only persists through a single test.
Our unit to be tested with the undesired decorator:
# app/uut.py from app.decorators import func_decor @func_decor def unit_to_be_tested(): # Do stuff pass
From decorators module:
# app/decorators.py def func_decor(func): def inner(*args, **kwargs): print "Do stuff we don't want in our test" return func(*args, **kwargs) return inner
By the time our test gets collected during a test run, the undesired decorator has already been applied to our unit under test (because that happens at import time). In order to get rid of that, we'll need to manually replace the decorator in the decorator's module and then re-import the module containing our UUT.
Our test module:
# test_uut.py from unittest import TestCase from app import uut # Module with our thing to test from app import decorators # Module with the decorator we need to replace import imp # Library to help us reload our UUT module from mock import patch class TestUUT(TestCase): def setUp(self): # Do cleanup first so it is ready if an exception is raised def kill_patches(): # Create a cleanup callback that undoes our patches patch.stopall() # Stops all patches started with start() imp.reload(uut) # Reload our UUT module which restores the original decorator self.addCleanup(kill_patches) # We want to make sure this is run so we do this in addCleanup instead of tearDown # Now patch the decorator where the decorator is being imported from patch('app.decorators.func_decor', lambda x: x).start() # The lambda makes our decorator into a pass-thru. Also, don't forget to call start() # HINT: if you're patching a decor with params use something like: # lambda *x, **y: lambda f: f imp.reload(uut) # Reloads the uut.py module which applies our patched decorator
The cleanup callback, kill_patches, restores the original decorator and re-applies it to the unit we were testing. This way, our patch only persists through a single test rather than the entire session -- which is exactly how any other patch should behave. Also, since the clean up calls patch.stopall(), we can start any other patches in the setUp() we need and they will get cleaned up all in one place.
The important thing to understand about this method is how the reloading will affect things. If a module takes too long or has logic that runs on import, you may just need to shrug and test the decorator as part of the unit. :( Hopefully your code is better written than that. Right?
If one doesn't care if the patch is applied to the whole test session, the easiest way to do that is right at the top of the test file:
# test_uut.py from mock import patch patch('app.decorators.func_decor', lambda x: x).start() # MUST BE BEFORE THE UUT GETS IMPORTED ANYWHERE! from app import uut
Make sure to patch the file with the decorator rather than the local scope of the UUT and to start the patch before importing the unit with the decorator.
Interestingly, even if the patch is stopped, all the files that already imported will still have the patch applied to the decorator, which is the reverse of the situation we started with. Be aware that this method will patch any other files in the test run that are imported afterwards -- even if they don't declare a patch themselves.
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