Mocking open(file_name) in unit tests

2020-02-02 11:25发布

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?

8条回答
甜甜的少女心
2楼-- · 2020-02-02 11:25

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')]
查看更多
Rolldiameter
3楼-- · 2020-02-02 11:30

@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)
查看更多
乱世女痞
4楼-- · 2020-02-02 11:36

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.

查看更多
Rolldiameter
5楼-- · 2020-02-02 11:39

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

查看更多
Rolldiameter
6楼-- · 2020-02-02 11:39

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")
        # ...
查看更多
ゆ 、 Hurt°
7楼-- · 2020-02-02 11:45

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'))

<the rest of your test setup goes here>

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)

<rest of test setup >

mocker.replay()

<test>

mocker.restore() #required to restore the open method
mocker.verify()

Hope this helps!

查看更多
登录 后发表回答