diff --git a/changelog/10156.doc.rst b/changelog/10156.doc.rst new file mode 100644 index 00000000000..fbddbdfeaa3 --- /dev/null +++ b/changelog/10156.doc.rst @@ -0,0 +1,2 @@ +Added a documentation example showing how to build dynamic session-scoped +parametrized fixtures whose ``params`` depend on command-line options. diff --git a/doc/en/example/special.rst b/doc/en/example/special.rst index ace37c72784..df183052613 100644 --- a/doc/en/example/special.rst +++ b/doc/en/example/special.rst @@ -1,5 +1,79 @@ +.. _`dynamic session-scoped fixtures`: + +Building dynamic session-scoped parametrized fixtures +------------------------------------------------------ + +Sometimes you need a session-scoped fixture whose ``params`` depend on +command-line arguments or other runtime configuration that isn't available +when ``conftest.py`` is first imported. A plain ``@pytest.fixture`` definition +cannot do this, because fixture ``params`` are evaluated at import time, before +:func:`pytest_configure` runs. + +The solution is to register a *plugin class* containing the fixture inside +:func:`pytest_configure`. At that point the command-line options have already +been parsed, so you can build the ``params`` list dynamically. + +.. code-block:: python + + # content of conftest.py + + import pytest + + + def pytest_addoption(parser): + parser.addoption( + "--servers", + default="localhost", + help="Comma-delimited list of servers to run tests against.", + ) + + + def pytest_configure(config): + # Options are available here, after argument parsing. + server_list = [s.strip() for s in config.getoption("--servers").split(",")] + + class DynamicFixturePlugin: + @pytest.fixture(scope="session", params=server_list) + def server_hostname(self, request): + """Parametrized session-scoped fixture built from --servers.""" + return request.param + + config.pluginmanager.register(DynamicFixturePlugin(), "server-hostname-fixture") + +Now any test that requests ``server_hostname`` will be run once per server: + +.. code-block:: python + + # content of test_servers.py + + + def test_responds(server_hostname): + assert server_hostname # replace with real connection logic + +Running with the default (one server): + +.. code-block:: pytest + + $ pytest -q test_servers.py + . + 1 passed in 0.12s + +Running against multiple servers: + +.. code-block:: pytest + + $ pytest -q test_servers.py --servers=host1,host2 + .. + 2 passed in 0.12s + +.. note:: + + The plugin class can contain multiple fixtures. Each fixture defined on the + class is treated exactly like a fixture in ``conftest.py`` — the ``self`` + parameter is the plugin instance and is not passed to the test. + A session-fixture which can look at all collected tests ----------------------------------------------------------------- +------------------------------------------------------- A session-scoped fixture effectively has access to all collected test items. Here is an example of a fixture