在 mock class 中使用 pytest fixture

Use pytest fixture inside mock class

我需要使用一个 fixture 在 class 中准备一些数据,这些数据将用于模拟第三方库。现在我有相当于这个:

@pytest.fixture(scope="session")
def file(tmpdir_factory):
    """Long process that creates a mock file."""
    ...
    return file_path
    

我需要在 class 构造函数中使用这个固定装置,例如:

class Mock:

    def __init__(self, file):
        self._file = file

    def get(self, *args, **kwargs):
        return self._file

我需要 file 夹具在 Mock class 之外,因为它在其他地方使用。 Mock class 的用法与此非常相似:


def my_test():
    with patch("thirdparty.Class", new=Mock):
        ...

我尝试使用 @pytest.mark.usefixtures("file") 装饰器,但它不起作用。如何将夹具注入 class?

如果您用新的 class Mock 修补了 thirdparty.Class,那么这意味着在源代码中实例化 thirdparty.Class 的所有调用都将使用 Mock相反。

解决方案 1

为了能够注入要在 class Mock 中使用的夹具 file,您必须在 Mock class 的某处定义它] 可以访问。您无法从 __init__ 控制它,因为它将从源代码中调用。您可以做的是将 class Mock 放入函数或夹具中,然后将 file 作为 function/fixture 本身中的变量访问。

thirdparty.py

class MyClass:
    def __init__(self, file):
        self._file = file

    def get(self, *args, **kwargs):
        return self._file


def func():
    obj = MyClass("/path/to/real")
    file = obj.get()
    print("File to process:", file)
    return file

test_thirdparty.py

from unittest.mock import patch

import pytest

from thirdparty import func


@pytest.fixture(scope="session")
def file():
    return "/path/to/mock"


@pytest.fixture
def my_mock_class(file):  # This can also be an ordinary function (not a fixture). You just need to pass the <file>.
    class MyMockClass:
        def __init__(self, *args, **kwargs):
            self._file = file  # Ignore the entered file in the initialization (__init__). Instead, read the injected file from the current fixture itself (my_mock_class).

        def get(self, *args, **kwargs):
            return self._file

    return MyMockClass


def test_real_file():
    assert func() == "/path/to/real"


def test_mock_file(my_mock_class):
    with patch("thirdparty.MyClass", new=my_mock_class):
        assert func() == "/path/to/mock"

输出

$ pytest -q -rP
..                                                             [100%]
=============================== PASSES ===============================
___________________________ test_real_file ___________________________
------------------------ Captured stdout call ------------------------
File to process: /path/to/real
___________________________ test_mock_file ___________________________
------------------------ Captured stdout call ------------------------
File to process: /path/to/mock
2 passed in 0.05s

解决方案 2

在源代码中,找到实例化它的那些:

the_class = thirdparty.Class(some_file)

然后,跟踪创建 some_file 的位置。假设这是来自函数调用:

some_file = get_file()

然后您需要修补 get_file() 到 return fixture file 的值,以便在创建 thirdparty.Class 时(或者更确切地说 Mock 因为我们已经修补了它),self._file 的值将是夹具中的值。

mocker.patch('get_file", return_value=file)  # Where <file> is the fixture