9

I am trying to unit test a piece of code:

def _parse_results(self, file_name):
    results_file = open(file_name)
    results_data = list(csv.reader(results_file))
    index = len(results_data[1])-1
    results_file.close()
    return float(results_data[1][index])

by using mock_open like so:

@mock.patch('path.open', mock.mock_open(read_data='test, test2, test3, test4'))
def test_parse_results(self):
    cut = my_class(emulate=True)
    self.assertEqual(VAL, cut._parse_results('file'))

The problem I am running into is that I do not get any data when running csv.reader. If I run results_file.readlines() I get 'test, test2, test3, test4' which means that mock_open is working properly. But when I run csv.reader(results_file) I lose all the data.

draB1
  • 63
  • 2
  • 7
  • I think this is because the csv is actually a _csv.c, a compiled c module, so it looks like the mocks are not carried over? – nadersoliman Jul 15 '16 at 12:38

2 Answers2

11

This is because mock_open doesn't implement every feature that a file has, and notably not some of the ones that csv needs.

mock_open implements the methods read(), readline() and readlines(), and works both as a function and when called as a context manager (https://docs.python.org/3/library/unittest.mock.html#mock-open), whereas csv.reader works with…

any object which supports the iterator protocol and returns a string each time its __next__() method is called — file objects and list objects are both suitable

https://docs.python.org/3/library/csv.html#csv.reader

Note that mock_open doesn't implement the __next__() method, and doesn't raise StopIteration when the end is reached, so it won't work with csv.reader.

The solution, as @Emily points out in her answer, is to turn the file into a list of its lines. This is possible because mock_open implements readlines(), and the resulting list is suitable for reading into csv.reader as the documentation says.

nimasmi
  • 3,978
  • 1
  • 25
  • 41
  • 1
    Another solution to testing csv.reader (and csv.DiscReader) is to implement the __next__() method on the MagicMock mock_open returns. As seen here: https://stackoverflow.com/a/24779923/484127 – tutuDajuju Mar 20 '18 at 13:37
  • Agreed. Implementing `__next__()` is a tidy approach. – nimasmi Oct 16 '18 at 08:49
4

This really got me too, and was a nightmare to pinpoint. To use your example code, this works

results_data = list(csv.reader(results_file.read()))

and this works

results_data = list(csv.reader(results_file.readlines()))

but this doesn't work

results_data = list(csv.reader(results_file))

using Python 3.4.

It seems counter to the documented interface of csv.reader so maybe an expert can elaborate on why.

wemily
  • 141
  • 3