|
2 | 2 | # E-mail: fuzzyman AT voidspace DOT org DOT uk |
3 | 3 | # http://www.voidspace.org.uk/python/mock/ |
4 | 4 |
|
| 5 | +import functools |
5 | 6 | import os |
6 | 7 | import sys |
7 | 8 | from collections import OrderedDict |
@@ -1226,6 +1227,40 @@ def unbound_f(self, n): |
1226 | 1227 | ) |
1227 | 1228 |
|
1228 | 1229 |
|
| 1230 | + def test_autospec_stacked_functools_wraps_decorators(self): |
| 1231 | + # a method wrapped by 2+ layers of functools.wraps decorators will have |
| 1232 | + # a chain of __wrapped__ attributes. autospec must still see the |
| 1233 | + # original method's (self, ...) signature through that chain and |
| 1234 | + # strip `self`, rather than falling back to (*args, **kwargs) and |
| 1235 | + # miscounting positional arguments. |
| 1236 | + def bar(f): |
| 1237 | + @functools.wraps(f) |
| 1238 | + def wrapper(*args, **kwargs): |
| 1239 | + return f(*args, **kwargs) |
| 1240 | + return wrapper |
| 1241 | + |
| 1242 | + def tender(f): |
| 1243 | + @functools.wraps(f) |
| 1244 | + def wrapper(*args, **kwargs): |
| 1245 | + return f(*args, **kwargs) |
| 1246 | + return wrapper |
| 1247 | + |
| 1248 | + class Foo: |
| 1249 | + @bar |
| 1250 | + @tender |
| 1251 | + def f(self, a, b): |
| 1252 | + return ('real', a, b) |
| 1253 | + |
| 1254 | + with patch.object(Foo, 'f', autospec=True, wraps=Foo.f) as method: |
| 1255 | + foo = Foo() |
| 1256 | + self.assertEqual(foo.f(1, 2), ('real', 1, 2)) |
| 1257 | + method.assert_called_once_with(1, 2) |
| 1258 | + |
| 1259 | + self.assertRaises(TypeError, foo.f) |
| 1260 | + self.assertRaises(TypeError, foo.f, 1) |
| 1261 | + self.assertRaises(TypeError, foo.f, 1, 2, 3) |
| 1262 | + |
| 1263 | + |
1229 | 1264 | def test_autospec_with_new(self): |
1230 | 1265 | patcher = patch('%s.function' % __name__, new=3, autospec=True) |
1231 | 1266 | self.assertRaises(TypeError, patcher.start) |
|
0 commit comments