使用 mock.patch 和 mock_open 模拟包含 JSON 数据的文件

Wak*_*u44 4 python python-3.x python-unittest python-unittest.mock

我正在尝试测试一种需要json.load在 Python 3.6 中使用的方法。经过多次尝试,我尝试“正常”运行测试(使用来自 CLI 的常用 unittest.main()),并在 iPython REPL 中运行。

具有以下功能(为了示例而简化)

def load_metadata(name):
    with open("{}.json".format(name)) as fh:
        return json.load(fh)
Run Code Online (Sandbox Code Playgroud)

通过以下测试:

class test_loading_metadata(unittest2.TestCase):
    @patch('builtins.open', new_callable=mock_open(read_data='{"disabled":True}'))
    def test_load_metadata_with_disabled(self, filemock):
        result = load_metadata("john")
        self.assertEqual(result,{"disabled":True})
        filemock.assert_called_with("john.json")
Run Code Online (Sandbox Code Playgroud)

测试文件的执行结果令人心碎:

TypeError: the JSON object must be str, bytes or bytearray, not 'MagicMock'
Run Code Online (Sandbox Code Playgroud)

在命令行中执行同样的事情时,给出了一个成功的结果。

我尝试了几种方法(用with,作为装饰器修补),但我唯一能想到的是unittest库本身,以及它可能会做什么来干扰模拟和补丁。

还检查了 virtualenv 和 ipython 中的 python 版本,json库的版本。

我想知道为什么看起来相同的代码在一个地方工作而在另一个地方不起作用。或者至少是一个指向正确方向的指针,以了解为什么会发生这种情况。

Mar*_*ers 5

json.load()只是调用fh.read(),但fh不是mock_open()对象。这是一个mock_open()()对象,因为new_callable称为修补创建替换对象之前:

>>> from unittest.mock import patch, mock_open
>>> with patch('builtins.open', new_callable=mock_open(read_data='{"disabled":True}')) as filemock:
...     with open("john.json") as fh:
...         print(fh.read())
...
<MagicMock name='open()().__enter__().read()' id='4420799600'>
Run Code Online (Sandbox Code Playgroud)

不要使用new_callable,你不希望你的mock_open()对象被调用!只需将其作为new参数传递给@patch()(这也是第二个位置参数,因此您可以省略new=此处):

@patch('builtins.open', mock_open(read_data='{"disabled":True}'))
def test_load_metadata_with_disabled(self, filemock):
Run Code Online (Sandbox Code Playgroud)

此时您可以.read()在用作open()函数时调用它:

>>> with patch('builtins.open', mock_open(read_data='{"disabled":True}')) as filemock:
...     with open("john.json") as fh:
...         print(fh.read())
...
{"disabled":True}
Run Code Online (Sandbox Code Playgroud)

new论点是修补时会替换原始的对象。如果保留为默认值,new_callable()则改为使用。你不想要new_callable()这里。