0

I have a decorator @auth which basically checks a database to ensure a user can access a given REST call. I would like to write some unit tests for these calls. My initial idea was to simply monkeypatch the decorator into a pass through that does nothing. (My initial idea failed, so I may just monkeypatch some function inside of @auth so that it always passes, but I'm still curious if I can bypass a decorator completely)

I threw together a quick sample of what I was hoping to accomplish.

example.py

# example.py
from __future__ import print_function

def sample_decorator(func):
    def decorated(*args, **kwargs):
        print("Start Calculation")
        ans = func(*args, **kwargs) + 3
        print(ans)
        print("Finished")
        return ans
    return decorated

@sample_decorator
def add(a, b):
    return a + b

test_example.py

# test_example.py
from __future__ import print_function
import pytest

import example

def test_add_with_decorator():
    assert example.add(1, 1) == 5

def testadd_with_monkeypatch_out_decorator(monkeypatch):
    monkeypatch.setattr(example, 'sample_decorator', lambda func: func)
    assert example.add(1, 1) == 2  # this fails, but is the behaviour I want

Is there some straight forward way to accomplish this?

Shatnerz
  • 2,353
  • 3
  • 27
  • 43
  • The function is wrapped when the module is loaded, it's too late to patch the decorator by the time you do so. But you may be able to access the wrapped version, if you're using Python 3.2+ and that decorator uses `functools.wraps`: http://stackoverflow.com/a/25909794/3001761 – jonrsharpe Dec 18 '16 at 08:17
  • See also [Can I patch a Python decorator before it wraps a function?](http://stackoverflow.com/questions/7667567/can-i-patch-a-python-decorator-before-it-wraps-a-function) – Peter Wood Dec 18 '16 at 20:50

2 Answers2

7

The decorator can set an attribute on the wrapping function to give access to the wrapped function.

Something along the line of

def wrap_foo(func):
 def decorated(*args, **kwargs):
  func(*args, **kwargs)
 decorated.__wrapped__ = func
 return decorated

@wrap_foo
def foo():
 pass

# Wrapped
foo()

# Unwrapped
foo.__wrapped__()
user2722968
  • 13,636
  • 2
  • 46
  • 67
  • It would be nice if this were possible without modifying the decorator, but this still looks like the most straight forward approach – Shatnerz Dec 18 '16 at 18:12
  • Worth noting: Python is practically standardised today on using the name ``__wrapped__`` instead of ``__wrapped_fn``; this is used both by wrappers such as `functools.wraps` and unwrappers such as `inspect.unwrap`. – MisterMiyagi Dec 15 '20 at 11:50
-1

Just keep the definitions separate:

def raw_add...

add = sample_decorator(raw_add)

assert example.raw_add...
Alex Hall
  • 34,833
  • 5
  • 57
  • 89