Reputation: 989
I'm trying to conditionally mock something in a function-scoped fixture differently for different tests, so I don't have to create two entirely separate fixtures with nearly-identical code.
MY_GLOB_VAR = False
def mocked_build_data():
with conditional(MY_GLOB_VAR, mock.patch(stuff)):
build_data()
class Tests:
@pytest.fixture(scope='function')
def my_fixture(self, conftest_fixtures):
inject_test_specific_data_into_db(conftest_fixtures)
mocked_build_data()
yield
cleanup_mysql()
def test_1(self, my_fixture):
assert True
@mock.patch(f'{__name__}.MY_GLOB_VAR', True)
def test_2(self, my_fixture):
assert True
I need the mock on test_2
to be applied before my_fixture
is executed, but it's only being applied once I'm in the function body.
Can I either force this the way I'm doing it, or conditionally mock the fixture some other way?
Upvotes: 3
Views: 2195
Reputation: 21878
You can use the indirect parametrization feature of Pytest for that.
Using the
indirect=True
parameter when parametrizing a test allows to parametrize a test with a fixture receiving the values before passing them to a test.
In your case you could mock the object in the fixture according to the value it receives (through the request
param). Here is an example showing the principle.
import pytest
MY_GLOB_VAR = None
@pytest.fixture(scope="function")
def my_fixture(request):
if hasattr(request, "param"):
# use it without param (default case)
global MY_GLOB_VAR
MY_GLOB_VAR = request.param
yield
# restore initial status
MY_GLOB_VAR = None
@pytest.mark.parametrize("my_fixture", [True], indirect=True)
def test_true(my_fixture):
assert MY_GLOB_VAR == True
@pytest.mark.parametrize("my_fixture", [False], indirect=True)
def test_false(my_fixture):
assert MY_GLOB_VAR == False
def test_none(my_fixture):
"""Example without parametrization"""
assert MY_GLOB_VAR is None
Run the tests
pytest test_mock.py
# test_mock.py::test_true[True] PASSED
# test_mock.py::test_false[False] PASSED
# test_mock.py::test_none PASSED ```
Upvotes: 1
Reputation: 66181
You can also move patching in a separate fixture and make sure it is requested before my_fixture
in test_2
. Fixtures are executed in the order they are listed in test function arguments, so in the signature def test(fix1, fix2):
the fixture fix1
will be executed before fix2
etc. Specifically for your example:
MY_GLOB_VAR = False
class Tests:
@pytest.fixture
def patch_my_glob_var(self):
with mock.patch(f'{__name__}.MY_GLOB_VAR', True):
yield
@pytest.fixture(scope='function')
def my_fixture(self):
print('MY_GLOB_VAR is', MY_GLOB_VAR)
yield
def test_1(self, my_fixture):
assert MY_GLOB_VAR is False
def test_2(self, patch_my_glob_var, my_fixture):
assert MY_GLOB_VAR is True
Running it with the -s
flag should yield
some_test_module::Tests::test_1 MY_GLOB_VAR is False
PASSED
some_test_module::Tests::test_2 MY_GLOB_VAR is True
PASSED
Another option would be requesting the fixture via the usefixtures
marker, since its returned result isn't explicitly used in the test. This will lead to the same result though, as patch_my_glob_var
will still be evaluated before my_fixture
:
@pytest.mark.usefixtures('patch_my_glob_var')
def test_2(self, my_fixture):
...
Upvotes: 3