如何修复调用中的mock_open差异,但不是最终结果

时间:2014-03-05 14:46:42

标签: python json mocking

使用mock_open,我可以使用with [...] as构造从写入中捕获数据。但是,测试我所拥有的是正确的有点棘手。例如,我可以这样做:

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

但我想比较一下我认为应该写的是什么。实际上是这样的:

>>> expected = 'some stuff'
>>> assert(expected == m.all_that_was_written)

我面临的问题是call,不同版本的json(2.0.9 vs 1.9)似乎以不同的方式打印。不,我不能只更新到最新的json。

我得到的实际错误是:

E           AssertionError: [call('Tool_000.json', 'w'),
                             call().__enter__(),
                             call().write('['),
                             call().write('\n  '),
                             call().write('"1.0.0"'),
                             call().write(', \n  '),
                             call().write('"2014-02-27 08:58:02"'),
                             call().write(', \n  '),
                             call().write('"ook"'),
                             call().write('\n'),
                             call().write(']'),
                             call().__exit__(None, None, None)] 
            !=
                            [call('Tool_000.json', 'w'),
                             call().__enter__(),
                             call().write('[\n  "1.0.0"'),
                             call().write(', \n  "2014-02-27 08:58:02"'),
                             call().write(', \n  "ook"'),
                             call().write('\n'),
                             call().write(']'),
                             call().__exit__(None, None, None)]

在效果中,调用是不同的,但最终结果是相同的。

我正在测试的代码非常简单:

with open(get_new_file_name(), 'w') as fp:
    json.dump(lst, fp)

因此,创建另一个传递文件指针的方法似乎有些过分。

3 个答案:

答案 0 :(得分:2)

您可以修补open()以返回StringIO对象,然后检查内容。

with mock.patch('module_under_test.open', create=True) as mock_open:
    stream = io.StringIO()
    # patching to make getvalue() work after close() or __exit__()
    stream.close = mock.Mock(return_value=None)
    mock_open.return_value = stream

    module_under_test.do_something() # this calls open()

    contents = stream.getvalue()
    assert(contents == expected)

修改:为stream.close添加了补丁,以避免stream.getvalue()上发生异常。

答案 1 :(得分:2)

mock_open还没有完整功能。如果您要模拟要读取的文件但它还没有足够的功能来测试写入的文件,它可以很好地工作。这个问题清楚地表明了这种不足。

我的解决方案是,如果您正在测试书面内容,请不要使用mock_open。这是另一种选择:

import six
import mock
import unittest

class GenTest(unittest.TestCase):
    def test_open_mock(self):
        io = six.BytesIO()
        io_mock = mock.MagicMock(wraps=io)
        io_mock.__enter__.return_value = io_mock
        io_mock.close = mock.Mock() # optional
        with mock.patch.object(six.moves.builtins, 'open', create=True, return_value=io_mock):
            # test using with
            with open('foo', 'w') as h:
                expected = 'some stuff'
                h.write(expected)
            self.assertEquals(expected, io.getvalue())
            # test using file handle directly
            io.seek(0); io.truncate() # reset io
            expected = 'other stuff'
            open('bar', 'w').write(expected)
            self.assertEquals(expected, io.getvalue())
            # test getvalue after close
            io.seek(0); io.truncate() # reset io
            expected = 'closing stuff'
            f = open('baz', 'w')
            f.write(expected)
            f.close()
            self.assertEquals(expected, io.getvalue())

if __name__ == '__main__':
    unittest.main()

答案 2 :(得分:1)

这是我要做的,编写一个方法,从write方法的所有调用中返回完整的字符串。

class FileIOTestCase(unittest.TestCase):
    """ For testing code involving file io operations """
    def setUp(self):
        """ patches the open function with a mock, to be undone after test. """
        self.mo = mock_open()

        patcher = patch("builtins.open", self.mo)
        patcher.start()
        self.addCleanup(patcher.stop)

    def get_written_string(self):
        return ''.join(c[0][0] for c in self.mo.return_value.write.call_args_list)

如何使用它的一个例子

class TestWriteFile(FileIOTestCase):
    def test_write_file__csv(self):
        save.write_file("a,b\n1,2", "directory", "C6L")
        self.mo.assert_called_once_with(os.path.join("directory", "C6L.csv"), 'w')
        self.assertEqual(self.get_written_string(), "a,b\n1,2")