Issue #26750: unittest.mock.create_autospec() now works properly
for subclasses of property() and other data descriptors.
Backports: 9854789efec0c707fff871b32b2833f32b078fb3
Signed-off-by: Chris Withers <[email protected]>
The cpython patch removed two tests and replaced with one, I've just added the new test paranoidly, so we keep the old ones as the diff didn't apply cleanly.
diff --git a/NEWS b/NEWS
index 6eb2d30..7f52276 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,9 @@
Library
-------
+- Issue #26750: unittest.mock.create_autospec() now works properly for
+ subclasses of property() and other data descriptors.
+
- Issue #21271: New keyword only parameters in reset_mock call.
- Issue #26807: mock_open 'files' no longer error on readline at end of file.
diff --git a/mock/mock.py b/mock/mock.py
index 51877cf..b2f5c75 100644
--- a/mock/mock.py
+++ b/mock/mock.py
@@ -154,12 +154,20 @@
__slots__ = ['a']
+# Do not use this tuple. It was never documented as a public API.
+# It will be removed. It has no obvious signs of users on github.
DescriptorTypes = (
type(_slotted.a),
property,
)
+def _is_data_descriptor(obj):
+ # Data descriptors are Properties, slots, getsets and C data members.
+ return ((hasattr(obj, '__set__') or hasattr(obj, '__del__')) and
+ hasattr(obj, '__get__'))
+
+
def _get_signature_object(func, as_instance, eat_self):
"""
Given an arbitrary, possibly callable object, try to create a suitable
@@ -2300,7 +2308,7 @@
_kwargs.update(kwargs)
Klass = MagicMock
- if type(spec) in DescriptorTypes:
+ if _is_data_descriptor(spec):
# descriptors don't have a spec
# because we don't know what type they return
_kwargs = {}
diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py
index 2b2053f..66dbb3f 100644
--- a/mock/tests/testhelpers.py
+++ b/mock/tests/testhelpers.py
@@ -890,6 +890,56 @@
mock_slot.assert_called_once_with(1, 2, 3)
mock_slot.abc.assert_called_once_with(4, 5, 6)
+
+ def test_autospec_data_descriptor(self):
+ class Descriptor(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __get__(self, obj, cls=None):
+ if obj is None:
+ return self
+ return self.value
+
+ def __set__(self, obj, value):
+ pass
+
+ class MyProperty(property):
+ pass
+
+ class Foo(object):
+ __slots__ = ['slot']
+
+ @property
+ def prop(self):
+ return 3
+
+ @MyProperty
+ def subprop(self):
+ return 4
+
+ desc = Descriptor(42)
+
+ foo = create_autospec(Foo)
+
+ def check_data_descriptor(mock_attr):
+ # Data descriptors don't have a spec.
+ self.assertIsInstance(mock_attr, MagicMock)
+ mock_attr(1, 2, 3)
+ mock_attr.abc(4, 5, 6)
+ mock_attr.assert_called_once_with(1, 2, 3)
+ mock_attr.abc.assert_called_once_with(4, 5, 6)
+
+ # property
+ check_data_descriptor(foo.prop)
+ # property subclass
+ check_data_descriptor(foo.subprop)
+ # class __slot__
+ check_data_descriptor(foo.slot)
+ # plain data descriptor
+ check_data_descriptor(foo.desc)
+
+
def test_autospec_on_bound_builtin_function(self):
meth = six.create_bound_method(time.ctime, time.time())
self.assertIsInstance(meth(), str)