12

I want to test a simple decorator I wrote:

It looks like this:

#utilities.py
import other_module
def decor(f):
    @wraps(f)
    def wrapper(*args, **kwds):
            other_module.startdoingsomething()
        try:
            return f(*args, **kwds)
        finally:
            other_module.enddoingsomething()
    return wrapper

Then I test it using python-mock:

#test_utilities.py
def test_decor(self):
    mock_func = Mock()
    decorated_func = self.utilities.decor(mock_func)
    decorated_func(1,2,3)
    self.assertTrue(self.other_module.startdoingsomething.called)
    self.assertTrue(self.other_module.enddoingsomething.called)
    mock_func.assert_called_with(1,2,3)

But it kicks back with:

Traceback (most recent call last):
  File "test_utilities.py", line 25, in test_decor
    decorated_func = Mock(wraps=self.utilities.decor(mock_func))
  File "utilities.py", line 35, in decor
    @wraps(f)
  File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 660, in __getattr__
    raise AttributeError(name)
AttributeError: __name__

I know functools.wraps() is just a helper wrapper. So if I take it out the test works.

Can I get Mock to play nice with functools.wraps()?

Python 2.7.3

double-beep
  • 5,031
  • 17
  • 33
  • 41
powlo
  • 2,538
  • 3
  • 28
  • 38

1 Answers1

16

Just give your mock that attribute:

mock_func.__name__ = 'foo'

That's it really.

Demo:

>>> from functools import wraps
>>> from mock import Mock
>>> def decor(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         return f(*args, **kwds)
...     return wrapper
... 
>>> mock_func = Mock()
>>> decor(mock_func)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in decor
  File ".../opt/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  File ".../lib/python2.7/site-packages/mock.py", line 660, in __getattr__
    raise AttributeError(name)
AttributeError: __name__
>>> mock_func.__name__ = 'foo'
>>> decor(mock_func)
<function foo at 0x10c4321b8>

Setting __name__ is perfectly fine; the @wraps decorator simply copies over the __name__ attribute to the wrapper, and on function objects that attribute is normally set to a string value. It's a writable attribute on functions, in any case, and as long as you use strings function.__name__ can be set to any value.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Yeah that's what I just did. I wasn't sure if there were serious ramifications though. Such as there being a "proper" value. But the test passes, so I guess no problem?! Seems it was raised and closed here: https://code.google.com/p/mock/issues/detail?id=67 – powlo Mar 05 '14 at 17:38
  • 1
    @paulus_almighty: Any string value will do. Anything you can use on `function.__name__`, which is anything as long as the type is `str`. – Martijn Pieters Mar 05 '14 at 17:42