Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python mock builtin 'open' in a class using two different files

Tags:

I am having trouble figuring out how to mock two file opens in a class when they both use context managers. I know how to do it for one context-managed file using the mock module like this:

@patch('__builtin__.open') def test_interface_mapping(self, mock_config):         m = MagicMock(spec=file)         handle = m.return_value.__enter__.return_value         handle.__iter__.return_value = ('aa', 'bb') 

My problem is how to do this when a class opens two different files in the same call. In my case, the class __init__() preloads the files into two maps. This class is used in other classes. I want to mock the loading of these two files to provide my test data so that the other classes that use the IfAddrConfig object can be tested against my preloaded test file content.

Here's an example of the class I am struggling with that loads two files in __init__(), both of which I want to mock to load my test injected file contents. getInterfaceMap() is the function that is called frequently so I do not want that to be loading and parsing the files every call, hence the reason for preloading the maps in __init__() once.

class IfAddrConfig(object):     def __init__(self):         # Initialize the static maps once since they require file operations         # that we do not want to be calling every time getInterfaceMap() is used         self.settings_map = self.loadSettings()         self.config_map = self.loadConfig()      def loadConfig(self):         config_map = defaultdict(dict)         with open(os.path.join('some_path.cfg'), 'r') as stream:             for line in stream:                 # Parse line and build up config_map entries         return config_map      def loadSettings(self):         settings_map = {}         with open('another_path.cfg', 'r') as stream:             for line in stream:                 # Parse line and build up settings_map entries         return settings_map      def getInterfaceMap(self, interface):         # Uses both the settings and config maps to finally create a composite map         # that is returned to called         interface_map = {}         for values in self.config_map.values():             # Accesss self.settings_map and combine/compare entries with             # self.config_map values to build new composite mappings that             # depend on supplied interface value         return interface_map 
like image 959
chromeeagle Avatar asked Nov 06 '14 15:11

chromeeagle


1 Answers

You must use side_effect attribute of your patched open object (mock_open) and don't forget to set the return_value for __exit__ method.

@patch('__builtin__.open', spec=open) def test_interface_mapping(self, mock_open):     handle1 = MagicMock()     handle1.__enter__.return_value.__iter__.return_value = ('aa', 'bb')     handle1.__exit__.return_value=False     handle2 = MagicMock()     handle2.__enter__.return_value.__iter__.return_value = ('AA', 'BB')     handle2.__exit__.return_value=False     mock_open.side_effect = (handle1, handle2)     with open("ppp") as f:         self.assertListEqual(["aa","bb"],[x for x in f])     with open("ppp") as f:         self.assertListEqual(["AA","BB"],[x for x in f]) 

[EDIT] I found a much more elegant way to do it Mock builtin 'open" function when used in contextlib

So you can rewrote test like

@patch('__builtin__.open', new_callable=mock_open, read_data="aa\nbb") def test_interface_mapping_new(self, mo):     handlers = (mo.return_value,mock_open(read_data="AA\nBB").return_value,)     mo.side_effect = handlers     with open("ppp") as f:         self.assertEqual("aa\nbb",f.read())     with open("ppp") as f:         self.assertEqual("AA\nBB",f.read()) 

And from python 3.4 you can use also readline(), readlines() without mocking anything else.

like image 154
Michele d'Amico Avatar answered Nov 02 '22 15:11

Michele d'Amico