Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 04cbe0c

Browse files
committed
Closes issue 17467. Add readline and readlines support to unittest.mock.mock_open
1 parent 94f2788 commit 04cbe0c

4 files changed

Lines changed: 141 additions & 8 deletions

File tree

Doc/library/unittest.mock.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,8 +1989,12 @@ mock_open
19891989
default) then a `MagicMock` will be created for you, with the API limited
19901990
to methods or attributes available on standard file handles.
19911991

1992-
`read_data` is a string for the `read` method of the file handle to return.
1993-
This is an empty string by default.
1992+
`read_data` is a string for the `read`, `readline`, and `readlines` methods
1993+
of the file handle to return. Calls to those methods will take data from
1994+
`read_data` until it is depleted. The mock of these methods is pretty
1995+
simplistic. If you need more control over the data that you are feeding to
1996+
the tested code you will need to customize this mock for yourself.
1997+
`read_data` is an empty string by default.
19941998

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

Lib/unittest/mock.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -934,8 +934,6 @@ def _mock_call(_mock_self, *args, **kwargs):
934934
return result
935935

936936
ret_val = effect(*args, **kwargs)
937-
if ret_val is DEFAULT:
938-
ret_val = self.return_value
939937

940938
if (self._mock_wraps is not None and
941939
self._mock_return_value is DEFAULT):
@@ -2207,6 +2205,24 @@ def __init__(self, spec, spec_set=False, parent=None,
22072205

22082206
file_spec = None
22092207

2208+
def _iterate_read_data(read_data):
2209+
# Helper for mock_open:
2210+
# Retrieve lines from read_data via a generator so that separate calls to
2211+
# readline, read, and readlines are properly interleaved
2212+
data_as_list = ['{}\n'.format(l) for l in read_data.split('\n')]
2213+
2214+
if data_as_list[-1] == '\n':
2215+
# If the last line ended in a newline, the list comprehension will have an
2216+
# extra entry that's just a newline. Remove this.
2217+
data_as_list = data_as_list[:-1]
2218+
else:
2219+
# If there wasn't an extra newline by itself, then the file being
2220+
# emulated doesn't have a newline to end the last line remove the
2221+
# newline that our naive format() added
2222+
data_as_list[-1] = data_as_list[-1][:-1]
2223+
2224+
for line in data_as_list:
2225+
yield line
22102226

22112227
def mock_open(mock=None, read_data=''):
22122228
"""
@@ -2217,9 +2233,27 @@ def mock_open(mock=None, read_data=''):
22172233
default) then a `MagicMock` will be created for you, with the API limited
22182234
to methods or attributes available on standard file handles.
22192235
2220-
`read_data` is a string for the `read` method of the file handle to return.
2221-
This is an empty string by default.
2236+
`read_data` is a string for the `read` methoddline`, and `readlines` of the
2237+
file handle to return. This is an empty string by default.
22222238
"""
2239+
def _readlines_side_effect(*args, **kwargs):
2240+
if handle.readlines.return_value is not None:
2241+
return handle.readlines.return_value
2242+
return list(_data)
2243+
2244+
def _read_side_effect(*args, **kwargs):
2245+
if handle.read.return_value is not None:
2246+
return handle.read.return_value
2247+
return ''.join(_data)
2248+
2249+
def _readline_side_effect():
2250+
if handle.readline.return_value is not None:
2251+
while True:
2252+
yield handle.readline.return_value
2253+
for line in _data:
2254+
yield line
2255+
2256+
22232257
global file_spec
22242258
if file_spec is None:
22252259
import _io
@@ -2229,9 +2263,18 @@ def mock_open(mock=None, read_data=''):
22292263
mock = MagicMock(name='open', spec=open)
22302264

22312265
handle = MagicMock(spec=file_spec)
2232-
handle.write.return_value = None
22332266
handle.__enter__.return_value = handle
2234-
handle.read.return_value = read_data
2267+
2268+
_data = _iterate_read_data(read_data)
2269+
2270+
handle.write.return_value = None
2271+
handle.read.return_value = None
2272+
handle.readline.return_value = None
2273+
handle.readlines.return_value = None
2274+
2275+
handle.read.side_effect = _read_side_effect
2276+
handle.readline.side_effect = _readline_side_effect()
2277+
handle.readlines.side_effect = _readlines_side_effect
22352278

22362279
mock.return_value = handle
22372280
return mock

Lib/unittest/test/testmock/testwith.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,88 @@ def test_read_data(self):
172172
self.assertEqual(result, 'foo')
173173

174174

175+
def test_readline_data(self):
176+
# Check that readline will return all the lines from the fake file
177+
mock = mock_open(read_data='foo\nbar\nbaz\n')
178+
with patch('%s.open' % __name__, mock, create=True):
179+
h = open('bar')
180+
line1 = h.readline()
181+
line2 = h.readline()
182+
line3 = h.readline()
183+
self.assertEqual(line1, 'foo\n')
184+
self.assertEqual(line2, 'bar\n')
185+
self.assertEqual(line3, 'baz\n')
186+
187+
# Check that we properly emulate a file that doesn't end in a newline
188+
mock = mock_open(read_data='foo')
189+
with patch('%s.open' % __name__, mock, create=True):
190+
h = open('bar')
191+
result = h.readline()
192+
self.assertEqual(result, 'foo')
193+
194+
195+
def test_readlines_data(self):
196+
# Test that emulating a file that ends in a newline character works
197+
mock = mock_open(read_data='foo\nbar\nbaz\n')
198+
with patch('%s.open' % __name__, mock, create=True):
199+
h = open('bar')
200+
result = h.readlines()
201+
self.assertEqual(result, ['foo\n', 'bar\n', 'baz\n'])
202+
203+
# Test that files without a final newline will also be correctly
204+
# emulated
205+
mock = mock_open(read_data='foo\nbar\nbaz')
206+
with patch('%s.open' % __name__, mock, create=True):
207+
h = open('bar')
208+
result = h.readlines()
209+
210+
self.assertEqual(result, ['foo\n', 'bar\n', 'baz'])
211+
212+
213+
def test_mock_open_read_with_argument(self):
214+
# At one point calling read with an argument was broken
215+
# for mocks returned by mock_open
216+
some_data = 'foo\nbar\nbaz'
217+
mock = mock_open(read_data=some_data)
218+
self.assertEqual(mock().read(10), some_data)
219+
220+
221+
def test_interleaved_reads(self):
222+
# Test that calling read, readline, and readlines pulls data
223+
# sequentially from the data we preload with
224+
mock = mock_open(read_data='foo\nbar\nbaz\n')
225+
with patch('%s.open' % __name__, mock, create=True):
226+
h = open('bar')
227+
line1 = h.readline()
228+
rest = h.readlines()
229+
self.assertEqual(line1, 'foo\n')
230+
self.assertEqual(rest, ['bar\n', 'baz\n'])
231+
232+
mock = mock_open(read_data='foo\nbar\nbaz\n')
233+
with patch('%s.open' % __name__, mock, create=True):
234+
h = open('bar')
235+
line1 = h.readline()
236+
rest = h.read()
237+
self.assertEqual(line1, 'foo\n')
238+
self.assertEqual(rest, 'bar\nbaz\n')
239+
240+
241+
def test_overriding_return_values(self):
242+
mock = mock_open(read_data='foo')
243+
handle = mock()
244+
245+
handle.read.return_value = 'bar'
246+
handle.readline.return_value = 'bar'
247+
handle.readlines.return_value = ['bar']
248+
249+
self.assertEqual(handle.read(), 'bar')
250+
self.assertEqual(handle.readline(), 'bar')
251+
self.assertEqual(handle.readlines(), ['bar'])
252+
253+
# call repeatedly to check that a StopIteration is not propagated
254+
self.assertEqual(handle.readline(), 'bar')
255+
self.assertEqual(handle.readline(), 'bar')
256+
257+
175258
if __name__ == '__main__':
176259
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ Core and Builtins
289289
Library
290290
-------
291291

292+
- Issue #17467: add readline and readlines support to mock_open in
293+
unittest.mock.
294+
292295
- Issue #17192: Update the ctypes module's libffi to v3.0.13. This
293296
specifically addresses a stack misalignment issue on x86 and issues on
294297
some more recent platforms.

0 commit comments

Comments
 (0)