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.Class
in 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