AlexLordThorsen
AlexLordThorsen

Reputation: 8488

Mocking file handles

There's a couple of stack overflow posts out there talking about mocking the open call in Python. That's great but it doesn't really help me if a function takes in a file handle or stream object instead of a file path.

One solution I've been using up until now has been cStringIO objects. I've run into a problem, however.

If I want to test if I'm logging the file name correctly on some sort of failure (say if the file / stream is empty and you expect some kind of data)

cStringIO
fd = cStringIO("")
fd.name = "testing/path" # Throws an AttributeError

I can't set the name attribute since cStringIO and StringIO are slotted classes.

If switch over to using open_mock

with mock.patch('__main__.open', mock.mock_open(read_data=''), create=True) as m:

I run into

AttributeError: Mock object has no attribute 'tell'

At this point it feels like I have to use temp files but I'd like to avoid actually calling out to the file system if possible.

How do you test functions that take in file handles without having to create actual files on a file system?

Upvotes: 1

Views: 1829

Answers (2)

AlexLordThorsen
AlexLordThorsen

Reputation: 8488

I went down the path of creating a class that inherited from StringIO. It took me more time than I want to admit to figure out that in Python2.7 StringIO is an old style class.

class MockFile(StringIO, object):
    """This is a work around for the fact that StringIO is a slotted class and
    doesn't have a name attribute.
    """
    name = None
    def __init__(self, name, buffer_ = None):
        super(MockFile, self).__init__(buffer_)
        self.name = name

Upvotes: 0

falsetru
falsetru

Reputation: 369054

You can set the tell attribute explicitly for the mock object using Mock.return_value:

import mock

def function_under_test(f):
    f.tell()  # => 0
    f.read()
    f.tell()  # => 0
    return f.name

with mock.patch('__main__.open', mock.mock_open(read_data=''), create=True) as m:
    with open('/tmp/1') as f:
        f.name = '/tmp/1'
        f.tell.return_value = 0
        assert function_under_test(f) == '/tmp/1'

Upvotes: 1

Related Questions