How to Patch a Python Decorator Before It Wraps a Function

Can I patch a Python decorator before it wraps a function?

Decorators are applied at function definition time. For most functions, this is when the module is loaded. (Functions that are defined in other functions have the decorator applied each time the enclosing function is called.)

So if you want to monkey-patch a decorator, what you need to do is:

  1. Import the module that contains it
  2. Define the mock decorator function
  3. Set e.g. module.decorator = mymockdecorator
  4. Import the module(s) that use the decorator, or use it in your own module

If the module that contains the decorator also contains functions that use it, those are already decorated by the time you can see them, and you're probably S.O.L.

Edit to reflect changes to Python since I originally wrote this: If the decorator uses functools.wraps() and the version of Python is new enough, you may be able to dig out the original function using the __wrapped__ attribute and re-decorate it, but this is by no means guaranteed, and the decorator you want to replace also may not be the only decorator applied.

Mock a decorator function to bypass decorator logic

Since the decorator runs immediately after you define get_value, it's too late to mock the decorator. What you can do, though, (since you used functools.wraps) is mock get_value itself and use get_value.__wrapped__ (the original function) in some way. Something like

@patch('tmp.get_value', get_value.__wrapped__)
def test_something(self):
result = get_value(1)
self.assertEqual(1, result)

(In this case, I put your original code, with the above change, in tmp.py, and ran it as python3 -munittest tmp.py, hence my patching of the reference tmp.get_value.)

If you anticipate the need to test the undecorated original, though, it might be simpler to keep it under its own (private) name to test: no patching needed.

import unittest
from functools import wraps

def decorator(f):
@wraps(f)
def decorated(x):
return f(x+1)
return decorated

def _get_value(x):
return x

get_value = decorator(_get_value)

class MyTestCase(unittest.TestCase):
def test_something(self):
result = _get_value(1)
self.assertEqual(1, result)

How to mock a decorated function

Python applies the decorator when loading the module so setting function_to_be_mocked to mock_function in test_decoratorated_mocked would indeed change that function into an undecorated function.

You'd need to manually add the decorator again if you wish to mock function_to_be_mocked:

mydecorator.function_to_be_mocked = mydecorator.my_decorator(mock_function)

How to remove the effects of a decorator while testing in python?

The retry decorator you are using is built on top of the decorator.decorator utility decorator with a simpler fallback if that package is not installed.

The result has a __wrapped__ attribute that gives you access to the original function:

orig = _sftp_command_with_retries.__wrapped__

If decorator is not installed and you are using a Python version before 3.2, that attribute won't be present; you'd have to manually reach into the decorator closure:

orig = _sftp_command_with_retries.__closure__[1].cell_contents

(the closure at index 0 is the retry_decorator produced when calling retry() itself).

Note that decorator is listed as a dependency in the retry package metadata, and if you installed it with pip the decorator package would have been installed automatically.

You can support both possibilities with a try...except:

try:
orig = _sftp_command_with_retries.__wrapped__
except AttributeError:
# decorator.decorator not available and not Python 3.2 or newer.
orig = _sftp_command_with_retries.__closure__[1].cell_contents

Note that you always can patch time.sleep() with a mock. The decorator code will use the mock as it references the 'global' time module in the module source code.

Alternatively, you could patch retry.api.__retry_internal with:

import retry.api
def dontretry(f, *args, **kw):
return f()

with mock.patch.object(retry.api, '__retry_internal', dontretry):
# use your decorated method

This temporarily replaces the function that does the actual retrying with one that just calls your original function directly.

Python: decorator for mocking gettext during testing

I didn't find anything similar on the web, so I'm sharing it:

from unittest import mock
import functools

def mock_translation(tested_object):
"""
A decorator that mock the '_()' function during testing
just adds '_translated' after the string to translate

the class/function being tested needs to be passed as parameter

Use:
from my_module.sub_module import my_function_to_be_tested

@mock_translation(my_function_to_be_tested)
def test_my_function_to_be_tested():
pass
"""

def actual_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
module_name = tested_object.__module__
tested_obj = tested_object
breakpoint()
import_path = module_name + '._'
# with mock.patch('battletech.models._', lambda s: s+'_translated'):
with mock.patch(import_path, lambda s: s+'_translated'):
value = func(*args, **kwargs)
return value
return wrapper
return actual_decorator

pytest-monkeypatch a decorator (not using mock / patch)

It looks like the quick answer here is simply to move your Handler import so that it occurs after the patch. The decorator and the decorated functions must be in separate modules so that python doesn’t execute the decorator before you’ve patched it.

from decorators import user_login_required

@pytest.mark.parametrize('params', get_params, ids=get_ids)
def test_post(self, params, monkeypatch):

monkeypatch.setattr(decorators, "user_login_required" , mock_user_login_required_func)
from handlers.UserDetails import UserDetailsHandler

You may find this easier to accomplish using the patch function from the built in unittest.mock module.



Related Topics



Leave a reply



Submit