kissgyorgy
kissgyorgy

Reputation: 3010

How to unit test with a mocked file object in Python?

I have a class which I instantiate by giving a file name like parser = ParserClass('/path/to/file'), then I call parser.parse() method which opens and reads the file.
Now I want to unit test that if something bad happening inside:

with open(filename, 'rb') as fp:
    // do something

the correct Exception will be raised, so I want to mock the __builtin__.open like this:

from mock import MagicMock, patch
from StringIO import StringIO

test_lines = StringIO("""some test lines, emulating a real file content""")
mock_open = MagicMock(return_value=test_lines)
with patch('__builtin__.open', mock_open):
    self.mock.parse()

but this gives me an AttributeError: StringIO instance has no attribute '__exit__'.
I tought StringIO behaves exactly like a file object, but it seems, this is not the case.

How could I test this method with a given content (test_lines) with mock objects? What should I use instead?

Upvotes: 9

Views: 14911

Answers (4)

0xc0de
0xc0de

Reputation: 8287

There is a provision, specifically for this purpose in the mock library:

It does have an issue in missing support for default iterator (ie. __iter__ method, so you can't do for line in opened_mock_file straight away), but it can be worked around as described here.

I have upvoted @icecrime's answer, but I feel his update part doesn't seem to be highlighted enough.

Upvotes: 1

icecrime
icecrime

Reputation: 76755

This is a known issue that StringIO does not implement the context manager protocol.

A common recipe is the following:

from contextlib import contextmanager


@contextmanager
def StringIO():
    """Add support for 'with' statement to StringIO - http://bugs.python.org/issue1286
    """
    try:
        from cStringIO import StringIO
    except ImportError:
        from StringIO import StringIO

    sio = StringIO()

    try:
        yield sio
    finally:
        sio.close()

It implements the context manager protocol for StringIO and allows it to be used in a with statement.


UPDATE Well, I just discovered the existence of mock_open which can directly read from a string, so it's probably the way to go.

Upvotes: 8

chepner
chepner

Reputation: 531165

You could subclass StringIO to provide a context manager:

class ContextualStringIO(StringIO):
    def __enter__(self):
        return self
    def __exit__(self, *args):
        self.close() # icecrime does it, so I guess I should, too
        return False # Indicate that we haven't handled the exception, if received


test_lines = ContextualStringIO(...)

Gross speculation: if StringIO objects are drop-in replacements for file objects except for the lack of a context manager, I wonder if this would work:

class ContextualStringIO(StringIO, file):
    pass

ContextualStringIO inherits what file operations it can from StringIO, but everything else is inherited from file. It looks elegant, but probably requires extensive testing (or someone more familiar with Python internals to explain why this wouldn't work).

Upvotes: 10

kissgyorgy
kissgyorgy

Reputation: 3010

Alternatively, you can use the io.StringIO from the Standard Library:

The io module provides the Python interfaces to stream handling. Under Python 2.x, this is proposed as an alternative to the built-in file object, but in Python 3.x it is the default interface to access files and streams.

However (as the Note says) this can be used only with unicode type.

Upvotes: 2

Related Questions