Programming is the art of adding bugs to an empty text file.
unittest.mock capabilities of Python to prevent these bugs, is another art of its own. And one of the trickiest move of them all is how to mock a decorator, such as it doesn't go in the way of the function you want to test.
Let's consider the following function to test:
### module api.service from utils.decorators import decorator_in_the_way @decorator_in_the_way def function_to_be_tested(*args, **kwargs): # do something ...
The problem here is to write a unit test for
function_to_be_tested without invoking the decorator (which would make your test fail)
Problem: it's not possible to
@patch the decorator above the test function, as it is too late: in Python functions are decorated at module-loading time. So this:
### file service_test.py from unittest.mock import patch from .service import function_to_be_tested def mock_decorator = ... @patch("api.service.decorator_in_the_way", mock_decorator) def test_function_to_be_tested(): result = function_to_be_tested() assert ...
... simply does not work. The patched decorator will simply be ignored, as the function to be tested already has been instrumented with the original one.
Fortunately, there is a workaround. The idea is to patch the decorator before the module to test is loaded. Of course the requirement is that the decorator and the function to test don't belong to the same module
The following example will work:
### file service_test.py from unittest.mock import patch from functools import wraps def mock_decorator(*args, **kwargs): """Decorate by doing nothing.""" def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function return decorator # PATCH THE DECORATOR HERE patch('utils.decorators.decorator_in_the_way', mock_decorator).start() # THEN LOAD THE SERVICE MODULE from .service import function_to_be_tested # ALL TESTS OF THE TEST SESSION WILL USE THE PATCHED DECORATOR def test_function_to_be_tested(): result = function_to_be_tested() # uses the mock decorator assert ...
Hope this helps !
I’m Matthieu, data engineer at Stack Labs.
If you want to join an enthousiast Data Engineering or Cloud Developer team, please contact us.
Top comments (3)
Nice article, thanks!
How to be if we don't want to mock at a session level? When do we need to stop the patcher if we want to apply the patcher at a module level?
Indeed, this method will patch all decorators of the testing session. If you want to patch only a module, you will have to
stop()the patch after the test, and don't forget to reload the .services module to restore the true decorated functions.