Is it possible to globally set a default `ids` function for pytest's parametrize?

Question:

pytest.mark.parametrize accepts an ids argument which can be a callable, like this:


def test_id_builder(arg):
    if isinstance(arg, int):
        return str(arg)

    ... # more logic

@pytest.mark.parametrize('value', [1, 2], ids=test_id_builder)
def test_whatever(value):
    assert value > 0

This will generate two test cases, with the ids "1" and "2" respectively. The problem is that I have a lot of tests, organized in multiple classes and files. Because of that, I’d like to globally set test_id_builder as the ids function for all parametrized tests in my project. Is there a way to do this?

Asked By: Aran-Fey

||

Answers:

There is no way to globally set ids. but yo can use pytest-generate-tests to generate tests from some other fixture. that other fixture could be scoped to session which overall will mimic the intended behaviour.

Answered By: simpleranchero

You can make your custom parametrize:

import pytest

def id_builder(arg):
    if isinstance(arg, int):
        return str(arg) * 2

def custom_parametrize(*args, **kwargs):
    kwargs.setdefault('ids', id_builder)
    return pytest.mark.parametrize(*args, **kwargs)

@custom_parametrize('value', [1, 2])
def test_whatever(value):
    assert value > 0

And to avoid rewriting pytest.mark.parametrize to custom_parametrize everywhere use this well-known workaround:

old_parametrize = pytest.mark.parametrize

def custom_parametrize(*args, **kwargs):
    kwargs.setdefault('ids', id_builder)
    return old_parametrize(*args, **kwargs)

pytest.mark.parametrize = custom_parametrize
Answered By: sanyassh

Simply implement a custom pytest_make_parametrize_id hook. In your conftest.py:

def pytest_make_parametrize_id(config, val, argname):
    if isinstance(val, int):
        return f'{argname}={val}'
    if isinstance(val, str):
        return f'text is {val}'
    # return None to let pytest handle the formatting
    return None

Example tests:

import pytest


@pytest.mark.parametrize('n', range(3))
def test_int(n):
    assert True

@pytest.mark.parametrize('s', ('fizz', 'buzz'))
def test_str(s):
    assert True


@pytest.mark.parametrize('c', (tuple(), list(), set()))
def test_unhandled(c):
    assert True

Check the test parametrizing:

$ pytest -q --collect-only 
test_spam.py::test_int[n=0]
test_spam.py::test_int[n=1]
test_spam.py::test_int[n=2]
test_spam.py::test_str[text is fizz]
test_spam.py::test_str[text is buzz]
test_spam.py::test_unhandled[c0]
test_spam.py::test_unhandled[c1]
test_spam.py::test_unhandled[c2]

no tests ran in 0.06 seconds
Answered By: hoefling
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.