Skip to content
Merged
1 change: 1 addition & 0 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2731,6 +2731,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
_new_parent=parent,
**kwargs)
mock._mock_children[entry] = new
new.return_value = Mock()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use the child_klass to construct the mock object instead of Mock. This can break code that depended on the return value being an instance of child_klass. Example case as below :

from unittest.mock import create_autospec, seal

class Foo:

    def bar(self):
        pass

spec = create_autospec(Foo)
print(spec.bar())
print(len(spec.bar()))

Python 3.10

python3.10 gh92213.py  
<MagicMock name='mock.bar()' id='140499591708000'>
0

With patch

./python gh92213.py
<Mock name='mock.bar()' id='140578691284304'>
Traceback (most recent call last):
  File "/home/karthikeyan/stuff/python/cpython/gh92213.py", line 10, in <module>
    print(len(spec.bar()))
          ^^^^^^^^^^^^^^^
TypeError: object of type 'Mock' has no len()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me, I used Mock originally because I wasn't sure if MagicMock might "evade" seal() if set explicitly, but I tested it and it does not:

class Foo:
    x = 1
    def foo(self) -> int:
        return 0
foo=Foo()
foo = mock.create_autospec(foo)
foo.foo().x

==> AttributeError: mock.foo().x

I've pushed the update to use child_klass()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akulakov - rather than just manually testing this, please can you add a test to the test suite to ensure it doesn't change in the future?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjw296 that makes sense, I've expanded the unit test - thanks for noting this.

_check_signature(original, new, skipfirst=skipfirst)

# so functions created with _set_signature become instance attributes,
Expand Down
3 changes: 1 addition & 2 deletions Lib/unittest/test/testmock/testsealable.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def ban(self):
self.assertIsInstance(foo.Baz, mock.MagicMock)
self.assertIsInstance(foo.Baz.baz, mock.NonCallableMagicMock)
self.assertIsInstance(foo.Baz.ban, mock.MagicMock)
self.assertIsInstance(foo.bar2(), mock.Mock)

self.assertEqual(foo.bar1(), 'a')
foo.bar1.return_value = 'new_a'
Expand All @@ -211,8 +212,6 @@ def ban(self):
foo.foo()
with self.assertRaises(AttributeError):
foo.bar = 1
with self.assertRaises(AttributeError):
foo.bar2()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing @sobolevn or @corona10 added this assertion for a reason, perhaps they could comment?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Nikita is largely on a break from open-source stuff right now FYI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this is an incorrect assert since bar2 method is present on Foo and shouldn't raise. Removing the assert statement the AttributeError here asserted is for the exception that return_value is not found due to seal which this PR fixes and not that bar2 is not found.

./python Lib/unittest/test/testmock/testsealable.py
..............EE....
======================================================================
ERROR: test_seal_with_autospec (__main__.TestSealable.test_seal_with_autospec) (spec_set=True)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/test/testmock/testsealable.py", line 215, in test_seal_with_autospec
    foo.bar2()
    ^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1108, in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1112, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1184, in _execute_mock_call
    return self.return_value
           ^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 638, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: Mock object has no attribute 'return_value'

======================================================================
ERROR: test_seal_with_autospec (__main__.TestSealable.test_seal_with_autospec) (spec_set=False)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/test/testmock/testsealable.py", line 215, in test_seal_with_autospec
    foo.bar2()
    ^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1108, in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1112, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 1184, in _execute_mock_call
    return self.return_value
           ^^^^^^^^^^^^^^^^^
  File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 638, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: Mock object has no attribute 'return_value'

----------------------------------------------------------------------
Ran 19 tests in 0.121s

FAILED (errors=2)


foo.bar2.return_value = 'bar2'
self.assertEqual(foo.bar2(), 'bar2')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix an error when using a method of objects mocked with
:func:`unittest.mock.create_autospec` after it was sealed with
:func:`unittest.mock.seal` function.