Skip to content

Commit 2087023

Browse files
TonyFluryberkerpeksag
authored andcommitted
bpo-32933: Implement __iter__ method on mock_open() (GH-5974)
1 parent c704222 commit 2087023

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.8
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
@@ -2358,14 +2358,16 @@ def _read_side_effect(*args, **kwargs):
23582358
return type(read_data)().join(_state[0])
23592359

23602360
def _readline_side_effect():
2361+
yield from _iter_side_effect()
2362+
while True:
2363+
yield type(read_data)()
2364+
2365+
def _iter_side_effect():
23612366
if handle.readline.return_value is not None:
23622367
while True:
23632368
yield handle.readline.return_value
23642369
for line in _state[0]:
23652370
yield line
2366-
while True:
2367-
yield type(read_data)()
2368-
23692371

23702372
global file_spec
23712373
if file_spec is None:
@@ -2389,6 +2391,7 @@ def _readline_side_effect():
23892391
_state[1] = _readline_side_effect()
23902392
handle.readline.side_effect = _state[1]
23912393
handle.readlines.side_effect = _readlines_side_effect
2394+
handle.__iter__.side_effect = _iter_side_effect
23922395

23932396
def reset_data(*args, **kwargs):
23942397
_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)