【问题标题】:How do I mock a a file IO so that I can override the name attribute in a unit test?如何模拟文件 IO,以便可以在单元测试中覆盖 name 属性?
【发布时间】:2026-01-11 10:20:03
【问题描述】:

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

我已经阅读了这里的大量答案,所有答案都以各种方式解决了这个问题。我试过模拟修补builtins.open,我试过模拟修补在我的模块内被调用的open,但我一直遇到的主要错误是当我尝试访问假IO对象的.name属性时,我得到一个属性错误:

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

【问题讨论】:

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


    【解决方案1】:

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

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

    它通过将 mock_open() 返回的内容替换为 builtins.open 来修补 builtins.open,而不是您实际需要的 MagicMock 对象,因此将行更改为简单:

    @patch("builtins.open")
    

    它应该可以工作。

    【讨论】:

      【解决方案2】:

      您的测试实际上从未调用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)
      

      【讨论】: