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

时间:2019-03-04 20:17:15

标签: python python-3.x python-unittest python-unittest.mock

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

具有以下功能(出于示例目的而简化)

def load_metadata(name):
    with open("{}.json".format(name)) as fh:
        return json.load(fh)

通过以下测试:

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

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

TypeError: the JSON object must be str, bytes or bytearray, not 'MagicMock'

在命令行中执行相同的操作时,将获得成功的结果。

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

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

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

1 个答案:

答案 0 :(得分:2)

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

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

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

在用作.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}

new参数是修补时将替换原始对象的对象。如果保留默认值,则使用new_callable()。您不想在这里new_callable()