DEV Community

matthieucham for Stack Labs

Posted on • Updated on

 

How to mock a decorator in Python

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.

Top comments (3)

Collapse
 
banderoz profile image
banderoz

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?

Collapse
 
matthieucham profile image
matthieucham

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.

Collapse
 
yannlemonnier profile image
Yann Lemonnier

Nice one!!

Timeless DEV post...

Git Concepts I Wish I Knew Years Ago

The most used technology by developers is not Javascript.

It's not Python or HTML.

It hardly even gets mentioned in interviews or listed as a pre-requisite for jobs.

I'm talking about Git and version control of course.

One does not simply learn git