Writing a csv temporary file using tempfile

So I’m writing a test for this class (edited to be more clear):

class SpreadSheet(object):
    '''awesome docstring'''
    def __init__(self, filename):
        self.filename = filename
        self.table = []
        self.headers = []

        with open(self.filename) as csvfile:
            filereader = reader(csvfile, delimiter=',')
            for row in filereader:
                self.table.append(row)

    def create_headers(self, populations):
        ...code...

    def lookup_header(self, ltr):
        ...code...

    def write_header(self, targetfile):
        ...code...

that so far looks like this:

class TestSpreadSheet(unittest.TestCase):
    @contextmanager
    def make_fake_csv(self, data):
        self.fake_namefile = tempfile.NamedTemporaryFile(delete=False)
        with open(self.fake_namefile, 'w') as fake_csv:
            fake_writer = csv.writer(fake_csv)
            fake_writer.writerows(data)
        yield self.fake_namefile.name
        os.unlink(self.fake_namefile.name)

    def setUp(self):
        self.headers = []
        self.table = [
            ['Col1', 'Col2', 'Col3', 'Col4', 'Col5', 'Col6', 'Col7', 'Col8'],
            ['val1', 'val2', 'val3', 'val4', 'val5', 'val6', 'val7', 'val8'],
            ['val1', 'val2', 'val3', 'val4', 'val5', 'val6', 'val7', 'val8'],
            ['val1', 'val2', 'val3', 'val4', 'val5', 'val6', 'val7', 'val8']]

    def test___init__(self):
        with self.make_fake_csv(self.table) as temp_csv:
            spread_sheet = SpreadSheet(temp_csv)
            self.assertEqual(
                self.table, spread_sheet.table)

    ...tests for other functions...

And I get this error:

in make_fake_csv
with open(self.fake_namefile, 'w') as fake_csv:
TypeError: coercing to Unicode: need string or buffer, instance found

I’ve scoured many other topics like this that point to the use of tempfile to make a named object or something that can actually be called using with open.... And while I did actually get that to work, my issues was when I tried to use the csv package to format my self.table for me into a csv formatted raw “string” (like the raw input of a csv file in other words).

Any pointers on how I can test this differently or make the current code work? Again I’m trying to:

  1. figure out how to use csv to do all the formatting heavy-lifting to load up a fake csv file from my self.table so that I don’t have to make a huge string formatting expression
  2. make sure that the fake file works with with open as used in my original class SpreadSheet when the test is run
  3. can be used further to run the test of the other functions because they too need to instantiate SpreadSheet with a file in order to perform their functions.

And as a side question, is it “leaner” to make a fake “memory” file to do things like this (this is what I’m attempting above) or is is just simpler to make an actual temporary file on the disk and load it up during testing and use a tearDown() function to delete it?

Best answer

self.fake_namefile in your example is an instance of NamedTemporaryFile. When you do the open() call you need to pass a string containing the file name, not a NamedTemporaryFile instance. The name of the temporary file is available in the name variable.

with open(self.fake_namefile.name, 'w') as fake_csv:

Here’s some suggestions:

  • Let your Spreadsheet class take a file-like object rather than a filename. This makes it more generic and allows it to be used with other stream based objects. If you had this, there’d be no need to create a fake file, you could simply construct a StringIO instance for testing.
  • If you’re set on using a NamedTemporaryFile, I’d suggest to use it as a context manager directly as outlined in the other answer.
  • You don’t have to use the delete=True option to NamedTemporaryFile. Instead, wrap your entire test in the context manager as follows.

    def test_stuff(self): with tempfile.NamedTemporaryFile() as temp_csv: self.write_csv_test_data(temp_csv) # Create this to write to temp_csv file object. temp_csv.flush() temp_csv.seek(0)

        spread_sheet = SpreadSheet(temp_csv.name)
        # spread_sheet = SpreadSheet(temp_csv)  Use this if Spreadsheet takes a file-like object
        ...
    

Update:

Here’s an example using only file-like objects, there’s no disk file involved.

class SpreadSheet(object):
    '''awesome docstring'''
    def __init__(self, fileobj):
        self.table = []
        self.headers = []

        filereader = reader(fileobj, delimiter=',')
        for row in filereader:
            self.table.append(row)
    ...

It could then be used like this, assuming you were reading from a disk file:

with open(path) as csv_file:
    spreadsheet = Spreadsheet(csv_file)
    ....

And during testing, you can use the StringIO module to simulate a file on disk. The test then runs with data entirely in memory so is very fast.

import StringIO

class TestSpreadSheet(unittest.TestCase):
    def make_fake_csv(self, data):
        """Return a populdated fake csv file object for testing."""
        fake_csv = StringIO.StringIO()
        fake_writer = csv.writer(fake_csv)
        fake_writer.writerows(data)
        fake_csv.seek(0)
        return fake_csv
    ....

    def test___init__(self):
        temp_csv = self.make_fake_csv(self.table)
        spread_sheet = SpreadSheet(temp_csv)
        self.assertEqual(
            self.table, spread_sheet.table)