11

I have a decorator and I want to assert that certain methods in my code are decorated with it.

import functools

def decorator(func):
    def _check_something(*args, **kwargs):
        # some logic in here
        return func(*args, **kwargs)
    return functools.wraps(func)(_check_something)

class MyClass(object):

    @decorator
    def my_method(foo, bar):
        pass

How do I assert with unittest (unitttest2) that my_method has @decorator and no-one removed it, and it was not forgotten?

Evgeny
  • 6,533
  • 5
  • 58
  • 64
  • 3
    Do some checks that test if the function (it's not called a "method" in Python, btw) has the correct behaviour, raising the correct exceptions etc. If it does, everything is fine. – Sven Marnach Apr 04 '12 at 13:35
  • updated the example to have a real method instead of just a dangling module function. – Evgeny Apr 04 '12 at 13:44
  • With unit tests, you only test if the function does the right thing. The whole point of this is that people can refactor the actual implementation as they want, as long as they don't break the functionality. What you're trying to do has nothing to do with unit testing. – Niklas B. Apr 04 '12 at 14:04
  • @NiklasB.: [Not necessarily](http://en.wikipedia.org/wiki/White-box_testing), but in this case I wouldn't go that route. – Sven Marnach Apr 04 '12 at 14:05
  • @SvenMarnach : To my knowledge, functions in classes are called methods as well in Python ( see e.g., http://docs.python.org/tutorial/classes.html ) – Simeon Visser Apr 04 '12 at 14:07
  • @SimeonVisser: In the first version of the question, it has been a module-level function. – Sven Marnach Apr 04 '12 at 14:10
  • @SvenMarnach: Ah indeed, I see it in the revision history. – Simeon Visser Apr 04 '12 at 14:11
  • Possible duplicate of [Check if a function has a decorator](http://stackoverflow.com/questions/5489649/check-if-a-function-has-a-decorator) – Rodrigue Jun 12 '12 at 11:36
  • Possible duplicate of [Introspection to get decorator names on a method](http://stackoverflow.com/questions/3232024/introspection-to-get-decorator-names-on-a-method) – Rodrigue Jun 12 '12 at 11:49

2 Answers2

5

If for some reason you can't modify the decorator, you could also try checking for some characteristic of a closed variable.

In your example, you know that the original my_method is the only variable closed by the decorator, so you could:

assert (my_method.__closure__ and 
           my_method.__closure__[0].cell_contents.__name__ == my_method.__name__)
agf
  • 171,228
  • 44
  • 289
  • 238
3

You can do that by relying on your decorator to mark the wrapper function with an attribute, that you then assert.

A good practice is to have the decorator set a __wrapped__ attribute pointing to the original function on the returned wrapper.

thus:

def decorator(func):
    @functools.wraps(func)
    def _check_something(*args, **kwargs):
        # some logic in here
        return func(*args, **kwargs)
    _check_something.__wrapped__ = func   # <== add this
    return _check_something

and then, on your test code:

assert getattr(MyClass.my_method, "__wrapped__").__name__ == 'my_method'
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 1
    The attribute holding the wrapped function should be called [`__wrapped__`](http://docs.python.org/py3k/library/functools.html#functools.update_wrapper). – Sven Marnach Apr 04 '12 at 14:04
  • Evggeny: did you test the edits you made to my answer? retriveng hte method weithout the class `__dict__`, in Python 2.x, gives you a bound method - no the function object - I don't think the bound method copies the `__wrapped__` attribute of the underlying function. Oh - I tested it now, indeed, the method object proxies the accesses tothe function object attributes - I did not know that. – jsbueno Apr 04 '12 at 18:22
  • 6
    This would check for the presence of *some* decorator, but not necessarily the specific `decorator` function. Is there a way of testing the presence of a _specific_ decorator? – andersonvom Dec 23 '13 at 23:20