mardi 22 décembre 2015

How to mock open differently depending on the parameters passed to open()

My question is how to mock open in python, such that it reacts differently depending on the argument open() is called with. These are some different scenario's that should be possible:

  • open a mocked file; read preset contents, the basic scenario. This is already possible within the framework, I know.

    with patch.object(__builtin__, 'open', mock_open(read_data='content')):
        with open('a') as handle:
            print handle.read() # output: content
    
    
  • open two mocked files and have them give back different values for the read() method. Example code:

    with patch.object(__builtin__, 'open', mock_open(side_effect)):
        with open('a') as handle:
            print handle.read() # output: content
    
        with open('b') as handle:
            print handle.read() # output: different!
    
    

The second example you could do by doing something similar as in the mock_open code in the mock.py. This, by changing the line in mock_open that reads:

handle.read.return_value = read_data

with something like:

handle.read.side_effect = my_side_effect

# where:
values = ['content', 'different!']
def my_side_effect():
    return values.pop(0)

Okay, so that is not the issue either. However, what I want is the above except:

  • The order in which the files read() methods have been called is irrelevant, what is relevant is the file name argument that was supplied when calling open. So if I would open file a, its read method would always return content, while file b's read method would always return different! regardless of the order in which they have been called.
  • Furthermore, if I call open('actual_file.txt') to open an actual file, I want the actual file to be opened, and not a magic mock with mocked behavior.

To this effect I have written the following code:

from mock import MagicMock
from mock import patch
import __builtin__

values = {'a':1, 'b': 2, 'c':3}

def my_mock_open(original_open, read_data):

    file_spec = file

    def my_side_effect(arg):
        if arg in values:
            mock = MagicMock(name='open', spec=original_open)
            handle = MagicMock(spec=file_spec)
            handle.write.return_value = None
            handle.__enter__.return_value = handle
            handle.read.return_value = values[arg]
            mock.return_value = handle
            return mock

        else:
            return original_open(arg)

    return_mock = MagicMock(name='open', spec=open)
    return_mock.side_effect = my_side_effect
    return return_mock

if __name__ == "__main__":
    with patch.object(__builtin__, 'open', my_mock_open(open, values)):
        with open('actual_file.txt') as handle:
            print handle.read()

        with open('a') as handle:
            print handle.read()

This code works for actual_file.txt. However, when I try to open a file that I wish to mock, such as with the open('a') call. I get the following error.

Traceback (most recent call last):
Some text inside content.txt
  File "D:/dev/python/scrap/src/main2.py", line 33, in <module>
    with open('a') as handle:
AttributeError: __exit__

Process finished with exit code 1

I have tried adding a line to my_side_effect where such as:

handle.__exit__.return_value = None
# or even:
handle.__exit__ = MagicMock()

None of these seem to solve the problem however. Could anyone shed some light on the issue please?

Aucun commentaire:

Enregistrer un commentaire