Programming is the art of adding bugs to an empty text file.
Mastering 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.