Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-32933: Implement dunder iter method on mock_open #5974

Merged
merged 13 commits into from Sep 12, 2018
4 changes: 4 additions & 0 deletions Doc/library/unittest.mock.rst
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
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
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
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
@@ -0,0 +1,2 @@
:func:`unittest.mock.mock_open` now supports iteration over the file
contents. Patch by Tony Flury.