How to mock a decorator in Python

matthieucham - Jan 26 '23 - - Dev Community

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
    ...
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

... 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 ...

Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .