How to skip parametrized tests with pytest

Question:

Is it possible to conditionally skip parametrized tests?
Here’s an example:

@pytest.mark.parametrize("a_date", a_list_of_dates)
@pytest.mark.skipif(a_date > date.today())
def test_something_using_a_date(self, a_date):
    assert <some assertion>

Of course I can do this inside the test method, but I’m looking for a structured way to do this with pytest.

Asked By: Jonathan Livni

||

Answers:

It is possible to do this, although depending on exactly what you want, it might take some work.

If you just need to skip specific parameter sets (i.e. if you don’t need to use an expression to identify which parameter sets to skip), it’s pretty easy:

@pytest.mark.parametrize("a", [
    1,
    pytest.param(2, marks=[pytest.mark.skip]),
])
def test_a(a):
    assert a == 1

If you do need to use an expression, then I think the best approach is to write a custom pytest_runtest_setup hook. This hook has access to the marks and parameters for each test, so it’s a good place to implement the kind of logic you want. The basic idea is to get the skip condition from a custom mark, evaluate that condition in the context of the parameters, then skip based on the result:

# conftest.py
import pytest

def pytest_runtest_setup(item):
    skip_funcs = [
            mark.args[0]
            for mark in item.iter_markers(name='parametrize_skip_if')
    ]
    if any(f(**item.callspec.params) for f in skip_funcs):
        pytest.skip()
# test_file.py
@pytest.mark.parametrize("b", [1, 2])
@pytest.mark.parametrize_skip_if(lambda b: b == 2)
def test_b(b):
    assert b == 1
Answered By: Kale Kundert

If you create you own method you check the values in test collection time and run the relevant tests only

a_list_of_dates = [date.today(), date(2024, 1, 1), date(2022, 1, 1)]

def get_dates():
    for d in a_list_of_dates:
        if d <= date.today():
            yield d

class TestSomething:
    @pytest.mark.parametrize("a_date", get_dates())
    def test_something_using_a_date(self, a_date):
        print(a_date)

Output

TestSomething::test_something_using_a_date[a_date0] PASSED [ 50%] 2022-08-24
TestSomething::test_something_using_a_date[a_date1] PASSED [100%] 2022-01-01

If you still want to the the skipped tests you can add the skip marker to the relevant tests

def get_dates():
    for d in a_list_of_dates:
        markers = []
        if d > date.today():
            markers.append(pytest.mark.skip(reason=f'{d} is after today'))
        yield pytest.param(d, marks=markers)

Output

TestSomething::test_something_using_a_date[a_date0] PASSED [ 33%] 2022-08-24

TestSomething::test_something_using_a_date[a_date1] SKIPPED (2024-01-01 is after today) [ 66%]
Skipped: 2024-01-01 is after today

TestSomething::test_something_using_a_date[a_date2] PASSED [100%] 2022-01-01
Answered By: Guy