Skip to content

Commit 6394e98

Browse files
authored
Restrict use of Mock objects as specs (GH-31090)
Follow-on to #25326 This covers cases where mock objects are passed directly to spec.
1 parent 8726067 commit 6394e98

File tree

4 files changed

+20
-3
lines changed

4 files changed

+20
-3
lines changed

Lib/unittest/mock.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ def mock_add_spec(self, spec, spec_set=False):
489489

490490
def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
491491
_eat_self=False):
492+
if _is_instance_mock(spec):
493+
raise InvalidSpecError(f'Cannot spec a Mock object. [object={spec!r}]')
494+
492495
_spec_class = None
493496
_spec_signature = None
494497
_spec_asyncs = []
@@ -2789,6 +2792,7 @@ def __init__(self, spec, spec_set=False, parent=None,
27892792

27902793

27912794
file_spec = None
2795+
open_spec = None
27922796

27932797

27942798
def _to_stream(read_data):
@@ -2845,8 +2849,12 @@ def _next_side_effect():
28452849
import _io
28462850
file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
28472851

2852+
global open_spec
2853+
if open_spec is None:
2854+
import _io
2855+
open_spec = list(set(dir(_io.open)))
28482856
if mock is None:
2849-
mock = MagicMock(name='open', spec=open)
2857+
mock = MagicMock(name='open', spec=open_spec)
28502858

28512859
handle = MagicMock(spec=file_spec)
28522860
handle.__enter__.return_value = handle

Lib/unittest/test/testmock/testmock.py

+8
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,14 @@ class B(object):
226226
with self.assertRaisesRegex(InvalidSpecError,
227227
"Cannot spec attr 'B' as the spec_set "):
228228
mock.patch.object(A, 'B', spec_set=A.B).start()
229+
with self.assertRaisesRegex(InvalidSpecError,
230+
"Cannot spec attr 'B' as the spec_set "):
231+
mock.patch.object(A, 'B', spec_set=A.B).start()
232+
with self.assertRaisesRegex(InvalidSpecError, "Cannot spec a Mock object."):
233+
mock.Mock(A.B)
234+
with mock.patch('builtins.open', mock.mock_open()):
235+
mock.mock_open() # should still be valid with open() mocked
236+
229237

230238
def test_reset_mock(self):
231239
parent = Mock()

Lib/unittest/test/testmock/testwith.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ def f(self): pass
130130

131131
c = C()
132132

133-
with patch.object(c, 'f', autospec=True) as patch1:
134-
with patch.object(c, 'f', autospec=True) as patch2:
133+
with patch.object(c, 'f') as patch1:
134+
with patch.object(c, 'f') as patch2:
135135
c.f()
136136
self.assertEqual(patch2.call_count, 1)
137137
self.assertEqual(patch1.call_count, 0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Mocks can no longer be provided as the specs for other Mocks. As a result, an already-mocked object cannot be passed to `mock.Mock()`. This can uncover bugs in tests since these Mock-derived Mocks will always pass certain tests (e.g. isinstance) and builtin assert functions (e.g. assert_called_once_with) will unconditionally pass.

0 commit comments

Comments
 (0)