如何根据传递给 open() 的参数以不同方式模拟打开

How to mock open differently depending on the parameters passed to open()

我的问题是如何在 python 中模拟打开,使其根据调用 open() 的参数做出不同的反应。这些是一些应该可能的不同场景:

我知道这个问题:Python mock builtin 'open' in a class using two different files。 但是这个答案只能部分回答第二个要求。关于顺序独立结果的部分不包括在内,它没有指定如何只模拟一些调用,并允许其他调用通过实际文件(默认行为)。

这可以通过遵循其他问题的已接受答案 (Python mock builtin 'open' in a class using two different files) 中的方法并进行一些改动来完成。

首先。而不是仅仅指定一个可以弹出的 side_effect 。我们需要确保 side_effect 可以 return 正确 mocked_file 取决于 open 调用使用的参数。

然后,如果我们希望打开的文件不在我们希望模拟的文件中,我们将 return 文件的原始 open() 而不是任何模拟行为。

下面的代码演示了如何以干净、可重复的方式实现这一点。例如,我将这段代码放在一个文件中,该文件提供了一些实用功能以使测试更容易。

from mock import MagicMock
import __builtin__
from mock import patch
import sys

# Reference to the original open function.
g__test_utils__original_open = open
g__test_utils__file_spec = None

def create_file_mock(read_data):

    # Create file_spec such as in mock.mock_open
    global g__test_utils__file_spec
    if g__test_utils__file_spec is None:
        # set on first use
        if sys.version_info[0] == 3:
            import _io
            g__test_utils__file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
        else:
            g__test_utils__file_spec = file

    file_handle = MagicMock(spec=g__test_utils__file_spec)
    file_handle.write.return_value = None
    file_handle.__enter__.return_value = file_handle
    file_handle.read.return_value = read_data
    return file_handle

def flexible_mock_open(file_map):
    def flexible_side_effect(file_name):
        if file_name in file_map:
            return file_map[file_name]
        else:
            global g__test_utils__original_open
            return g__test_utils__original_open(file_name)

    global g__test_utils__original_open
    return_value = MagicMock(name='open', spec=g__test_utils__original_open)
    return_value.side_effect = flexible_side_effect
    return return_value

if __name__ == "__main__":
    a_mock = create_file_mock(read_data="a mock - content")
    b_mock = create_file_mock(read_data="b mock - different content")
    mocked_files = {
        'a' : a_mock,
        'b' : b_mock,
    }
    with patch.object(__builtin__, 'open', flexible_mock_open(mocked_files)):
        with open('a') as file_handle:
            print file_handle.read() # prints a mock - content

        with open('b') as file_handle:
            print file_handle.read() # prints b mock - different content

        with open('actual_file.txt') as file_handle:
            print file_handle.read() # prints actual file contents

这直接从 mock.py (python 2.7) 中借用了一些代码来创建 file_spec。

旁注:如果有任何机构可以帮助我尽可能隐藏这些全局变量,那将非常有帮助。

有点晚了,但我最近刚好遇到同样的需求,所以我想分享我的解决方案,基于 this answer from the referred-to question:

import pytest
from unittest.mock import mock_open
from functools import partial
from pathlib import Path


mock_file_data = {
    "file1.txt": "some text 1",
    "file2.txt": "some text 2",
    # ... and so on ...
}


do_not_mock: {
    # If you need exact match (see note in mocked_file(),
    # you should replace these with the correct Path() invocations
    "notmocked1.txt",
    "notmocked2.txt",
    # ... and so on ...
}


# Ref: 
def mocked_file(m, fn, *args, **kwargs):
    m.opened_file = Path(fn)
    fn = Path(fn).name  # If you need exact path match, remove this line
    if fn in do_not_mock:
        return open(fn, *args, **kwargs)
    if fn not in mock_file_data:
        raise FileNotFoundError
    data = mock_file_data[fn]
    file_obj = mock_open(read_data=data).return_value
    file_obj.__iter__.return_value = data.splitlines(True)
    return file_obj


def assert_opened(m, fn):
    fn = Path(fn)
    assert m.opened_file == fn


@pytest.fixture()
def mocked_open(mocker):
    m = mocker.patch("builtins.open")
    m.side_effect = partial(mocked_file, m)
    m.assert_opened = partial(assert_opened, m)
    return m


def test_something(mocked_open):
    ...
    # Something that should NOT invoke open()
    mocked_open.assert_not_called()

    ...
    # Something that SHOULD invoke open()
    mocked_open.assert_called_once()
    mocked_open.assert_opened("file1.txt")
    # Depends on how the tested unit handle "naked" filenames,
    # you might have to change the arg to:
    #   Path.cwd() / "file1.txt"

    # ... and so on ...

请注意 (1) 我正在使用 Python 3,并且 (2) 我正在使用 pytest.