Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/unittest.mock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,10 @@ mock_open
.. versionchanged:: 3.5
*read_data* is now reset on each call to the *mock*.

.. versionchanged:: 3.8
Added :meth:`__iter__` to implementation so that iteration (such as in for
loops) correctly consumes *read_data*.

Using :func:`open` as a context manager is a great way to ensure your file handles
are closed properly and is becoming common::

Expand Down
9 changes: 6 additions & 3 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2356,14 +2356,16 @@ def _read_side_effect(*args, **kwargs):
return type(read_data)().join(_state[0])

def _readline_side_effect():
yield from _iter_side_effect()
while True:
yield type(read_data)()

def _iter_side_effect():
if handle.readline.return_value is not None:
while True:
yield handle.readline.return_value
for line in _state[0]:
yield line
while True:
yield type(read_data)()


global file_spec
if file_spec is None:
Expand All @@ -2387,6 +2389,7 @@ def _readline_side_effect():
_state[1] = _readline_side_effect()
handle.readline.side_effect = _state[1]
handle.readlines.side_effect = _readlines_side_effect
handle.__iter__.side_effect = _iter_side_effect

def reset_data(*args, **kwargs):
_state[0] = _iterate_read_data(read_data)
Expand Down
10 changes: 10 additions & 0 deletions Lib/unittest/test/testmock/testmock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,16 @@ def test_mock_open_reuse_issue_21750(self):
f2_data = f2.read()
self.assertEqual(f1_data, f2_data)

def test_mock_open_dunder_iter_issue(self):
# Test dunder_iter method generates the expected result and
# consumes the iterator.
mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue')
f1 = mocked_open('a-name')
lines = [line for line in f1]
self.assertEqual(lines[0], 'Remarkable\n')
self.assertEqual(lines[1], 'Norwegian Blue')
self.assertEqual(list(f1), [])

def test_mock_open_write(self):
# Test exception in file writing write()
mock_namedtemp = mock.mock_open(mock.MagicMock(name='JLV'))
Expand Down
15 changes: 15 additions & 0 deletions Lib/unittest/test/testmock/testwith.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def test_read_data(self):

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

# Check that we properly emulate a file that doesn't end in a newline
mock = mock_open(read_data='foo')
with patch('%s.open' % __name__, mock, create=True):
h = open('bar')
result = h.readline()
self.assertEqual(result, 'foo')
self.assertEqual(h.readline(), '')


def test_dunder_iter_data(self):
# Check that dunder_iter will return all the lines from the fake file.
mock = mock_open(read_data='foo\nbar\nbaz\n')
with patch('%s.open' % __name__, mock, create=True):
h = open('bar')
lines = [l for l in h]
self.assertEqual(lines[0], 'foo\n')
self.assertEqual(lines[1], 'bar\n')
self.assertEqual(lines[2], 'baz\n')
self.assertEqual(h.readline(), '')


def test_readlines_data(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`unittest.mock.mock_open` now supports iteration over the file
contents. Patch by Tony Flury.