Mocking open(file_name) in unit tests

匿名 (未验证) 提交于 2019-12-03 01:29:01

问题:

I have a source code that opens a csv file and sets up a header to value association. The source code is given below:

def ParseCsvFile(source):    """Parse the csv file.    Args:      source: file to be parsed    Returns: the list of dictionary entities; each dictionary contains              attribute to value mapping or its equivalent.    """    global rack_file    rack_type_file = None    try:      rack_file = source      rack_type_file = open(rack_file)  # Need to mock this line.     headers = rack_type_file.readline().split(',')      length = len(headers)      reader = csv.reader(rack_type_file, delimiter=',')      attributes_list=[] # list of dictionaries.      for line in reader:        # More process to happeng. Converting the rack name to sequence.        attributes_list.append(dict((headers[i],                                    line[i]) for i in range(length)))      return attributes_list    except IOError, (errno, strerror):      logging.error("I/O error(%s): %s" % (errno, strerror))    except IndexError, (errno, strerror):      logging.error('Index Error(%s), %s' %(errno, strerror))    finally:      rack_type_file.close()  

I am trying to mock the following statement

rack_type_file = open(rack_file)  

How do I mock open(...) function?

回答1:

This is admittedly an old question, hence some of the answers are outdated.

In the current version of the mock library there is a convenience function designed for precisely this purpose. Here's how it works:

>>> from mock import mock_open >>> m = mock_open() >>> with patch('__main__.open', m, create=True): ...     with open('foo', 'w') as h: ...         h.write('some stuff') ... >>> m.mock_calls [call('foo', 'w'),  call().__enter__(),  call().write('some stuff'),  call().__exit__(None, None, None)] >>> m.assert_called_once_with('foo', 'w') >>> handle = m() >>> handle.write.assert_called_once_with('some stuff') 

Documentation is here.



回答2:

To mock built-in function open with mox use __builtin__ module:

import __builtin__ # unlike __builtins__ this must be imported m = mox.Mox() m.StubOutWithMock(__builtin__, 'open') open('ftphelp.yml', 'rb').AndReturn(StringIO("fake file content"))      m.ReplayAll() # call the code you want to test that calls `open` m.VerifyAll() m.UnsetStubs() 

Note that __builtins__ is not always a module, it can be of type dict, please use __builtin__ (with no "s") module to refer to system built-in methods.

More about __builtin__ module: http://docs.python.org/library/builtin.html



回答3:

There are two ways that I like to do this, depending on the situation.

If your unit test is going to call ParseCsvFile directly I would add a new kwarg to ParseCsvFile:

def ParseCsvFile(source, open=open):      # ...     rack_type_file = open(rack_file)  # Need to mock this line. 

Then your unit test can pass a different open_func in order to accomplish the mocking.

If your unit test calls some other function that in turn calls ParseCsvFile then passing around open_func just for tests is ugly. In that case I would use the mock module. This lets you alter a function by name and replace it with a Mock object.

# code.py def open_func(name):     return open(name)  def ParseCsvFile(source):     # ...     rack_type_file = open_func(rack_file)  # Need to mock this line.  # test.py import unittest import mock from StringIO import StringIO  @mock.patch('code.open_func') class ParseCsvTest(unittest.TestCase):     def test_parse(self, open_mock):         open_mock.return_value = StringIO("my,example,input")         # ... 


回答4:

Is simple with decorator (Python3):

def my_method():     with open(file="/1.txt", mode='r', encoding='utf-8') as file:         return file.read().strip()   @mock.patch("builtins.open", create=True) def test_my_method(mock_open):     mock_open.side_effect = [         mock.mock_open(read_data="A").return_value     ]      resA = my_method()     assert resA == "A"      mock_open.mock_calls ==  [mock.call(file="/1.txt", mode='r', encoding='utf-8')] 


回答5:

I took the liberty of re-writing your sample function:

Assume your function is located in a file named code.py

# code.py import csv  import logging   def ParseCsvFile(source):     """Parse the csv file.     Args:       source: file to be parsed      Returns: the list of dictionary entities; each dictionary contains                attribute to value mapping or its equivalent.     """     global rack_file     rack_file = source     attributes_list = []      try:         rack_type_file = open(rack_file)     except IOError, (errno, strerror):         logging.error("I/O error(%s): %s", errno, strerror)     else:         reader = csv.DictReader(rack_type_file, delimiter=',')         attributes_list = [line for line in reader]   # list of dictionaries         rack_type_file.close()      return attributes_list 

A simple test case would be:

# your test file import __builtin__ import unittest import contextlib from StringIO import StringIO  import mox  import code   @contextlib.contextmanager def mox_replayer(mox_instance):     mox_instance.ReplayAll()     yield     mox_instance.VerifyAll()   class TestParseCSVFile(unittest.TestCase):      def setUp(self):         self.mox = mox.Mox()      def tearDown(self):         self.mox.UnsetStubs()      def test_parse_csv_file_returns_list_of_dicts(self):         TEST_FILE_NAME = 'foo.csv'          self.mox.StubOutWithMock(__builtin__, 'open')         open(TEST_FILE_NAME).AndReturn(StringIO("name,age\nfoo,13"))          with mox_replayer(self.mox):             result = code.ParseCsvFile(TEST_FILE_NAME)          self.assertEqual(result, [{'age': '13', 'name': 'foo'}])  # works!   if __name__ == '__main__':     unittest.main() 

EDIT:

% /usr/bin/python2.6 Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)  [GCC 4.2.1 (Apple Inc. build 5646)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import __builtin__ >>> import mox >>> mock = mox.Mox() >>> mock.StubOutWithMock(__builtin__, 'open') >>> mock.UnsetStubs() 

Works fine on 2.6 using mox 0.53



回答6:

Hi I was having a similar problem, and was tearing my hair out flipping between different mocking libraries. I finally found a solution that I am happy with, and maybe it might help you? In the end I went with the Mocker library http://labix.org/mocker and here is the code for mocking open:

from mocker import Mocker from StringIO import StringIO import __builtin__ mocker = Mocker() sourceFile = 'myTestFile.txt' __builtin__.open = mocker.mock() __builtin__.open(sourceFile) mocker.result(StringIO('this,is,a,test,file'))    mocker.replay()  ParseCsvFile(sourceFile)  mocker.restore() mocker.verify() 

Incidentaly the reason I went with Mocker is because I was testing a function which used open to read a file, and then used open again to overwrite the same file with new data. What I needed to be able to do was test the case where the initial file didn't exist, so set up a mock, that threw an IOError the first time, and then worked the second time. The setup for which looked like this:

from mocker import Mocker import __builtin__  mocker = Mocker()  mockFileObject = mocker.mock() __builtin__.open = mocker.mock()  __builtin__.open('previousState.pkl', 'r')  mocker.throw(IOError('Boom'))  __builtin__.open('previousState.pkl','w')  mocker.result(mockFileObject)    mocker.replay()    mocker.restore() #required to restore the open method mocker.verify() 

Hope this helps!



回答7:

>>> class A(object): ...     def __init__(self): ...         self.x = open('test.py') ...  >>> old_open = open >>> def open(s): ...     return "test\n" ...  >>> a = A() >>> a.x 'test\n' >>> open = old_open >>> a = A() >>> a.x 


回答8:

@mock.patch decorator (2.7 example)

This is now much easier:

import your_script.py import __builtin__ import mock   @mock.patch("__builtin__.open") def test_example(self, mock_open):     your_script.your_method()     self.assertEqual(mock_open.call_count, 1) 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!