unittest.mock spec calls class properties #85934
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
assignee = None closed_at = None created_at = <Date 2020-09-12.01:10:24.232> labels = ['3.8', 'type-bug', 'library', '3.9', '3.10'] title = 'unittest.mock spec calls class properties' updated_at = <Date 2021-12-03.08:45:33.635> user = 'https://github.com/melwitt'
activity = <Date 2021-12-03.08:45:33.635> actor = 'AlexWaygood' assignee = 'none' closed = False closed_date = None closer = None components = ['Library (Lib)'] creation = <Date 2020-09-12.01:10:24.232> creator = 'melwitt' dependencies =  files =  hgrepos =  issue_num = 41768 keywords = ['patch'] message_count = 3.0 messages = ['376757', '377140', '407569'] nosy_count = 9.0 nosy_names = ['terry.reedy', 'cjw296', 'michael.foord', 'lukasz.langa', 'lisroach', 'mariocj89', 'xtreak', 'sobolevn', 'melwitt'] pr_nums = ['22209'] priority = 'normal' resolution = None stage = 'patch review' status = 'open' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue41768' versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']
The text was updated successfully, but these errors were encountered:
When async magic method support was added to unittest.mock.Mock to address issue bpo-26467, it introduced a getattr call  that causes class properties to be called when the class is used as a mock spec.
This caused a problem for a test in my project when running with Python 3.8 where previously the test worked OK with Python 3.6.
The test aims to verify that a class instance is not created if the called code path does not access the class property and thus the class will not create a heavy object unless it's needed (lazy create on access via @Property).
Here is a code snippet that illustrates the @Property calling from the mock spec alone:
class SomethingElse(object): def __init__(self): self._instance = None @property def instance(self): if not self._instance: self._instance = 'object'
def test_property_not_called_with_spec_mock(self): obj = SomethingElse() self.assertIsNone(obj._instance) mock = Mock(spec=obj) self.assertIsNone(obj._instance)
$ ./python -m unittest -v unittest.test.testmock.testmock.MockTest.test_property_not_called_with_spec_mock test_property_not_called_with_spec_mock (unittest.test.testmock.testmock.MockTest) ... FAIL ====================================================================== FAIL: test_property_not_called_with_spec_mock (unittest.test.testmock.testmock.MockTest) \---------------------------------------------------------------------- Traceback (most recent call last): File "/home/vagrant/cpython/Lib/unittest/test/testmock/testmock.py", line 2173, in test_property_not_called_with_spec_mock self.assertIsNone(obj._instance) AssertionError: 'object' is not None
Without lines numbers, I cannot test which of the two identical asserts failed. Either comments or msg arguments will differentiate. Nor can I run snippets from two different files. Here is a minimal reproducible self-contained code that demonstrates the claim (which I verified on 3.9 and current master).
import unittest from unittest.mock import Mock class SomethingElse(object): def __init__(self): self._instance = None @property def instance(self): if not self._instance: self._instance = 'object' class Test(unittest.TestCase): def test_property_not_called_with_spec_mock(self): obj = SomethingElse() self.assertIsNone(obj._instance, msg='before') # before mock = Mock(spec=obj) self.assertIsNone(obj._instance, msg='after') # after unittest.main()
I'm affected by the same problem: The
There are three PRs and one issue proposing different solutions:
All solutions would work for the case depicted here. There is a corner case that is treated differently between the current code and the first three proposals:
class X(object): @property def foo(): async def bar(): pass return bar
If we spec an instance of this class,
The fourth solution will handle this corner case as the current code, but does not fix the issue discussed in this post, which was about avoiding expensive lazy initialization of objects triggered by a property getter.