Skip to content

Commit c83c375

Browse files
miss-islingtonTonyFlury
authored andcommitted
bpo-32933: Implement __iter__ method on mock_open() (GH-5974)
(cherry picked from commit 2087023) Co-authored-by: Tony Flury <anthony.flury@btinternet.com>
1 parent b2ecb8b commit c83c375

File tree

5 files changed

+37
-3
lines changed

5 files changed

+37
-3
lines changed

Doc/library/unittest.mock.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,10 @@ mock_open
20952095
.. versionchanged:: 3.5
20962096
*read_data* is now reset on each call to the *mock*.
20972097

2098+
.. versionchanged:: 3.7.1
2099+
Added :meth:`__iter__` to implementation so that iteration (such as in for
2100+
loops) correctly consumes *read_data*.
2101+
20982102
Using :func:`open` as a context manager is a great way to ensure your file handles
20992103
are closed properly and is becoming common::
21002104

Lib/unittest/mock.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,14 +2356,16 @@ def _read_side_effect(*args, **kwargs):
23562356
return type(read_data)().join(_state[0])
23572357

23582358
def _readline_side_effect():
2359+
yield from _iter_side_effect()
2360+
while True:
2361+
yield type(read_data)()
2362+
2363+
def _iter_side_effect():
23592364
if handle.readline.return_value is not None:
23602365
while True:
23612366
yield handle.readline.return_value
23622367
for line in _state[0]:
23632368
yield line
2364-
while True:
2365-
yield type(read_data)()
2366-
23672369

23682370
global file_spec
23692371
if file_spec is None:
@@ -2387,6 +2389,7 @@ def _readline_side_effect():
23872389
_state[1] = _readline_side_effect()
23882390
handle.readline.side_effect = _state[1]
23892391
handle.readlines.side_effect = _readlines_side_effect
2392+
handle.__iter__.side_effect = _iter_side_effect
23902393

23912394
def reset_data(*args, **kwargs):
23922395
_state[0] = _iterate_read_data(read_data)

Lib/unittest/test/testmock/testmock.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,16 @@ def test_mock_open_reuse_issue_21750(self):
14501450
f2_data = f2.read()
14511451
self.assertEqual(f1_data, f2_data)
14521452

1453+
def test_mock_open_dunder_iter_issue(self):
1454+
# Test dunder_iter method generates the expected result and
1455+
# consumes the iterator.
1456+
mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue')
1457+
f1 = mocked_open('a-name')
1458+
lines = [line for line in f1]
1459+
self.assertEqual(lines[0], 'Remarkable\n')
1460+
self.assertEqual(lines[1], 'Norwegian Blue')
1461+
self.assertEqual(list(f1), [])
1462+
14531463
def test_mock_open_write(self):
14541464
# Test exception in file writing write()
14551465
mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV'))

Lib/unittest/test/testmock/testwith.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ def test_read_data(self):
188188

189189
def test_readline_data(self):
190190
# Check that readline will return all the lines from the fake file
191+
# And that once fully consumed, readline will return an empty string.
191192
mock = mock_open(read_data='foo\nbar\nbaz\n')
192193
with patch('%s.open' % __name__, mock, create=True):
193194
h = open('bar')
@@ -197,13 +198,27 @@ def test_readline_data(self):
197198
self.assertEqual(line1, 'foo\n')
198199
self.assertEqual(line2, 'bar\n')
199200
self.assertEqual(line3, 'baz\n')
201+
self.assertEqual(h.readline(), '')
200202

201203
# Check that we properly emulate a file that doesn't end in a newline
202204
mock = mock_open(read_data='foo')
203205
with patch('%s.open' % __name__, mock, create=True):
204206
h = open('bar')
205207
result = h.readline()
206208
self.assertEqual(result, 'foo')
209+
self.assertEqual(h.readline(), '')
210+
211+
212+
def test_dunder_iter_data(self):
213+
# Check that dunder_iter will return all the lines from the fake file.
214+
mock = mock_open(read_data='foo\nbar\nbaz\n')
215+
with patch('%s.open' % __name__, mock, create=True):
216+
h = open('bar')
217+
lines = [l for l in h]
218+
self.assertEqual(lines[0], 'foo\n')
219+
self.assertEqual(lines[1], 'bar\n')
220+
self.assertEqual(lines[2], 'baz\n')
221+
self.assertEqual(h.readline(), '')
207222

208223

209224
def test_readlines_data(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`unittest.mock.mock_open` now supports iteration over the file
2+
contents. Patch by Tony Flury.

0 commit comments

Comments
 (0)