Home > database >  Use pytest fixture inside mock class
Use pytest fixture inside mock class

Time:09-17

I need to use a fixture that prepares some data inside a class that will be used to mock a third party library. Right now I have the equivalent to this:

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

I need to use this fixture inside the class constructor, something like:

class Mock:

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

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

I need the file fixture to be outside the Mock class since it is used in other places. The Mock class will be used very similar to this:


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

I tried using the @pytest.mark.usefixtures("file") decorator but it did not work. How can I inject the fixture to the class?

CodePudding user response:

If you patched thirdparty.Class with the new class Mock, then that means all calls to instantiate thirdparty.Classin the source code would use Mock instead.

Solution 1

To be able to inject the fixture file to be used inside the class Mock, you have to define it somewhere that the Mock class can access. You can't control it from the __init__ because that will be called from the source code. What you can do is put that class Mock inside a function or fixture and then access the file as a variable within the function/fixture itself.

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"

Output

$ 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

Solution 2

In the source code, find those that instantiates it:

the_class = thirdparty.Class(some_file)

Then, trace where some_file was created. Let's say this is from a function call:

some_file = get_file()

You then need to patch how get_file() to return the value of of the fixture file so that when thirdparty.Class is created (or rather Mock since we already patched it), the value for self._file would be the one in the fixture.

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