Pytest – dynamic resolution of fixtures' dependencies
Question:
I cannot find a solution to alter fixtures dependency in any different way than this bellow.
The problem is that I need to determine the dependencies basing on pytest.config.getoption
argument, instead of what’s used here (variable resolved at module level).
I need to get two modes of testing: fast and full, keeping the same test source code.
pytest_generate_tests
seems to be useless, or at least I don’t know how to use it here.
import pytest
DO_FULL_SETUP = "some condition that I need take from request.config.getoption(), not like this"
if DO_FULL_SETUP:
# such a distinction is valid from interpreter's (and pytest's) point of view
@pytest.fixture(scope="session")
def needed_environment(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
else:
@pytest.fixture
def needed_environment():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""
Answers:
This can be done using request.getfixturevalue('that_fixture_name')
. Fixtures can be invoked in runtime. There is even no fixture’s scope violation in this case ('session'
vs. 'function'
).
import pytest
@pytest.fixture(scope="session")
def needed_environment_full(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
@pytest.fixture
def needed_environment_fast():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
@pytest.fixture
def needed_environment(request):
"""Dynamically run a named fixture function, basing on cli call argument."""
if request.config.getoption('--run_that_fast'):
return request.getfixturevalue('needed_environment_fast')
else:
return request.getfixturevalue('needed_environment_full')
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""
An alternate technique is to follow the advice I laid out here with respect to leveraging the pytest_plugins
to select from different fixture implementations drawn from different plugin files at runtime but all sharing the same fixture identifier, in this case needed_environment
, you’d have a fast definition and a slow definition drawn from different plugin modules.
#environment_fixtures_fast.py
import pytest
@pytest.fixture
def needed_environment():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
#environment_fixtures_slow.py
import pytest
@pytest.fixture(scope="session")
def needed_environment(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
#test_module.py
import sys
if sys.argv[1] == "full":
pytest_plugins = ["environment_fixtures_slow"]
else
pytest_plugins = ["environment_fixtures_fast"]
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""
I cannot find a solution to alter fixtures dependency in any different way than this bellow.
The problem is that I need to determine the dependencies basing on pytest.config.getoption
argument, instead of what’s used here (variable resolved at module level).
I need to get two modes of testing: fast and full, keeping the same test source code.
pytest_generate_tests
seems to be useless, or at least I don’t know how to use it here.
import pytest
DO_FULL_SETUP = "some condition that I need take from request.config.getoption(), not like this"
if DO_FULL_SETUP:
# such a distinction is valid from interpreter's (and pytest's) point of view
@pytest.fixture(scope="session")
def needed_environment(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
else:
@pytest.fixture
def needed_environment():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""
This can be done using request.getfixturevalue('that_fixture_name')
. Fixtures can be invoked in runtime. There is even no fixture’s scope violation in this case ('session'
vs. 'function'
).
import pytest
@pytest.fixture(scope="session")
def needed_environment_full(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
@pytest.fixture
def needed_environment_fast():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
@pytest.fixture
def needed_environment(request):
"""Dynamically run a named fixture function, basing on cli call argument."""
if request.config.getoption('--run_that_fast'):
return request.getfixturevalue('needed_environment_fast')
else:
return request.getfixturevalue('needed_environment_full')
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""
An alternate technique is to follow the advice I laid out here with respect to leveraging the pytest_plugins
to select from different fixture implementations drawn from different plugin files at runtime but all sharing the same fixture identifier, in this case needed_environment
, you’d have a fast definition and a slow definition drawn from different plugin modules.
#environment_fixtures_fast.py
import pytest
@pytest.fixture
def needed_environment():
"""This does a fast setup, has "function scope"
and doesn't require any additional fixtures. Takes ~20ms"""
#environment_fixtures_slow.py
import pytest
@pytest.fixture(scope="session")
def needed_environment(a_lot_of, expensive, fixtures_needed):
"""This does expensive setup that I need
to avoid in "fast" mode. Takes about a minute (docker pull, etc..)"""
#test_module.py
import sys
if sys.argv[1] == "full":
pytest_plugins = ["environment_fixtures_slow"]
else
pytest_plugins = ["environment_fixtures_fast"]
def test_that_things(needed_environment):
"""At this moment I don't want to distinguish what
needed_environment is. Tests have to pass in both modes."""