tanaka
tanaka

Reputation: 401

Mocking "with open()"

I am trying to unit test a method that reads the lines from a file and process it.

with open([file_name], 'r') as file_list:
    for line in file_list:
        # Do stuff

I tried several ways described on another questions but none of them seems to work for this case. I don't quite understand how python uses the file object as an iterable on the lines, it internally use file_list.readlines() ?

This way didn't work:

    with mock.patch('[module_name].open') as mocked_open: # also tried with __builtin__ instead of module_name
        mocked_open.return_value = 'line1\nline2'

I got an

AttributeError: __exit__

Maybe because the with statement have this special attribute to close the file?

This code makes file_list a MagicMock. How do I store data on this MagicMock to iterate over it ?

with mock.patch("__builtin__.open", mock.mock_open(read_data="data")) as mock_file:

Best regards

Upvotes: 0

Views: 467

Answers (1)

chepner
chepner

Reputation: 531095

The return value of mock_open (until Python 3.7.1) doesn't provide a working __iter__ method, which may make it unsuitable for testing code that iterates over an open file object.

Instead, I recommend refactoring your code to take an already opened file-like object. That is, instead of

def some_method(file_name):
    with open([file_name], 'r') as file_list:
        for line in file_list:
            # Do stuff

...

 some_method(file_name)

write it as

def some_method(file_obj):
    for line in file_obj:
        # Do stuff

...

with open(file_name, 'r') as file_obj:
    some_method(file_obj)

This turns a function that has to perform IO into a pure(r) function that simply iterates over any file-like object. To test it, you don't need to mock open or hit the file system in any way; just create a StringIO object to use as the argument:

def test_it(self):
    f = StringIO.StringIO("line1\nline2\n")
    some_method(f)

(If you still feel the need to write and test a wrapper like

def some_wrapper(file_name):
    with open(file_name, 'r') as file_obj:
        some_method(file_obj)

note that you don't need the mocked open to do anything in particular. You test some_method separately, so the only thing you need to do to test some_wrapper is verify that the return value of open is passed to some_method. open, in this case, can be a plain old mock with no special behavior.)

Upvotes: 1

Related Questions