如何模拟文件 IO 以便我可以覆盖单元测试中的名称属性?

How do I mock a a file IO so that I can override the name attribute in a unit test?

我正在尝试编写一个单元测试模拟,打开一个文件并将其传递给一个函数,该函数使用它来将 JSON 对象转储到其中。如何创建模拟打开文件句柄行为但使用相似属性的假 IO 对象,特别是 .name?

我在这里阅读了大量的答案,他们都以不同的方式解决了这个问题。我试过模拟补丁 builtins.open,我试过模拟补丁 open 在我的模块中被调用,但是我保留 运行 的主要错误是当我尝试访问假 IO 对象的 .name 属性,我得到一个 AttributeError:

AttributeError: 'CallbackStream' object has no attribute 'name'

所以这是一个简单的函数,它以 JSON 格式将字典写入磁盘并获取打开的文件句柄:

def generate(data, json_file):
  # data is a dict
  logging.info(f"Writing out spec file to {json_file.name}")
  json.dump(data, json_file)

这是我尝试进行单元测试的内容:

    @patch("builtins.open", new_callable=mock_open())
    def test_generate_json_returns_zero(self, mock_open):
        mocked_file = mock_open()
        mocked_file.name = "FakeFileName"
        data = {'stuff': 'stuff2'}
        generate(data, json_file=mocked_file)

但是,这会产生上面的 AttributeError,我不能在其中使用 json_file.name,因为它不作为属性存在。我以为明确设置它会起作用,但它没有。

我可以通过`tempfile.TemporaryFile:

使用临时文件绕过这个问题
    def test_generate_json_returns_zero(self, mock_open):
        data = {'stuff': 'stuff2'}
        t = TemporaryFile("w")
        generate(data, json_file=t)

但这并没有解决 实际 问题,我不知道如何模拟文件句柄,这样我实际上就不需要创建磁盘上的真实文件。

我无法克服 .name 不是有效属性的问题。我如何模拟文件对象,以便我可以实际使用 IO 对象的 .name 属性并仍然伪造一个 json.dump() 给它?

new_callable 参数是 Mock 的替代 class,因此当您调用:

@patch("builtins.open", new_callable=mock_open())

它通过用 mock_open() returns 代替 builtins.open 来修补 builtins.open,而不是 MagicMock 对象,这是您实际需要的,所以将行更改为简单地:

@patch("builtins.open")

它应该可以工作。

您的测试从未实际调用 open,因此无需修补它。只需创建一个具有您需要的属性的 Mock 实例。

def test_generate_json_returns_zero(self):
    mocked_file = Mock()
    mocked_file.name = "FakeFileName"
    data = {'stuff': 'stuff2'}
    generate(data, json_file=mocked_file)