parametrize_with_fixtures.rst

Overview

This document proposes enhancements to how **pytest** fixtures can be combined and parametrized in functional tests, particularly when using fixtures as inputs to parametrized tests or other fixtures.

The primary goal is to enable testing multiple input scenarios cleanly and effectively, especially when those inputs are derived from existing fixtures. The proposal addresses current limitations when trying to parametrize tests that depend on other parametrized fixtures, which is a common challenge in pytest testing workflows.


Context & Problem Statement

Testers often want to run a suite of functional tests against different data scenarios. For example, when generating a project from a cookiecutter template, they might want to test:

A typical implementation might use multiple fixtures with hardcoded `params` lists and then a "combined" fixture that attempts to select between those. However, this approach is problematic:


Current Example (Problematic)

import pytest

@pytest.fixture
def default_context():
    return {"extra_context": {}}

@pytest.fixture(params=[
    {"author": "alice"},
    {"project_slug": "helloworld"},
    {"author": "bob", "project_slug": "foobar"},
])
def extra_context(request):
    return {"extra_context": request.param}

@pytest.fixture(params=["default", "extra"])
def context(request):
    if request.param == "default":
        return request.getfuncargvalue("default_context")
    else:
        return request.getfuncargvalue("extra_context")

def test_generate_project(cookies, context):
    result = cookies.bake(extra_context=context)
    assert result.exit_code == 0
    assert result.exception is None
    assert result.project.isdir()

This triggers errors because `extra_context` is parametrized but used dynamically via `getfuncargvalue`, which is incompatible with pytest parameter resolution.


Proposed Solution

Introduce a new pytest API function:

pytest.define_combined_fixture(
    name="context",
    fixtures=["default_context", "extra_context"]
)

This function creates a new fixture named `"context"` that:

For the above example, the combined fixture `context` will yield:

This approach resolves the dynamic fixture parameterization problem by declaratively combining fixtures.


Alternative Approach: fixture_request Helper

Another proposed helper function is `pytest.fixture_request()`. It can be used to wrap fixtures as parameters in a list, allowing tests or fixtures to parametrize over existing fixtures transparently.

Example usage:

@pytest.fixture(params=[
    pytest.fixture_request("default_context"),
    pytest.fixture_request("extra_context"),
])
def context(request):
    return request.param

Here, `context` yields all values from `default_context` and then all values from `extra_context` in sequence.

It also works with `pytest.mark.parametrize`:

@pytest.mark.parametrize(
    "context, expected_response_code",
    [
        (pytest.fixture_request("default_context"), 0),
        (pytest.fixture_request("extra_context"), 0),
    ],
)
def test_generate_project(cookies, context, exit_code):
    result = cookies.bake(extra_context=context)
    assert result.exit_code == exit_code

This allows easy extension of tests by adding fixture-based parameters to parameterized tests.


Implementation Details


Interactions with Other System Components


Usage Examples

  1. Define combined fixture:

pytest.define_combined_fixture(
    name="context",
    fixtures=["default_context", "extra_context"]
)
  1. Use fixture_request to parametrize a fixture:

@pytest.fixture(params=[
    pytest.fixture_request("default_context"),
    pytest.fixture_request("extra_context"),
])
def context(request):
    return request.param
  1. Use fixture_request in pytest.mark.parametrize:

@pytest.mark.parametrize(
    "context, expected_response_code",
    [
        (pytest.fixture_request("default_context"), 0),
        (pytest.fixture_request("extra_context"), 0),
    ],
)
def test_generate_project(cookies, context, exit_code):
    result = cookies.bake(extra_context=context)
    assert result.exit_code == exit_code

Visual Diagram

flowchart TD
    A[default_context fixture]
    B[extra_context fixture]
    C[define_combined_fixture: context]
    D[test_generate_project]

    A --> C
    B --> C
    C --> D

    subgraph "Alternative approach"
      E[fixture_request helper]
      F[context fixture with params using fixture_request]
      G[pytest.mark.parametrize using fixture_request]
      H[test_generate_project with parametrize]

      E --> F
      E --> G
      F --> H
      G --> H
    end

Summary

This proposal enhances pytest fixture parametrization by enabling:

It overcomes key limitations in pytest's fixture resolution and parameter passing mechanisms, improving test extensibility and maintainability.


References


This concludes the comprehensive documentation for **parametrize_with_fixtures.rst**.