Skip to content

Support fixtures as describe block arguments#54

Merged
Cito merged 2 commits into
mainfrom
describe-fixture-args
Jun 12, 2026
Merged

Support fixtures as describe block arguments#54
Cito merged 2 commits into
mainfrom
describe-fixture-args

Conversation

@Cito

@Cito Cito commented Jun 12, 2026

Copy link
Copy Markdown
Member

Closes #38, supersedes #39. Thanks to @ROpdebee for the original investigation.

Fixtures declared as parameters of a describe block (or a shared behavior) are injected into all tests nested in the block. Unlike the earlier PoC, test functions are not wrapped: the declared names are added to each test's fixture closure in a pytest_generate_tests hook (so pytest resolves them itself and parametrized fixtures multiply tests, @pytest.mark.parametrize keeps working), and an autouse fixture writes the resolved values into the closure cells before each test, restoring placeholders on teardown. Using an argument in the describe body itself raises a descriptive collection-time error.

Cito added 2 commits June 12, 2026 21:42
Proof of concept for injecting pytest fixtures declared as parameters
of describe blocks, shared by all tests nested in the block:

- At collection, the describe function is called with named placeholder
  objects, and the closure cells holding them are recorded.
- A pytest_generate_tests hook adds the declared names to the fixture
  closure (names_closure + initialnames + arg2fixturedefs), so pytest
  itself resolves the fixtures and parametrized fixtures multiply tests.
- Hook wrappers around test setup/teardown write the resolved fixture
  values into the closure cells and restore the placeholders afterwards.

Unlike the earlier proof of concept in PR #39, test functions are not
wrapped, so @pytest.mark.parametrize and signature introspection keep
working. Verified with tox against pytest 7.0-9.0 and latest on CPython
and against pytest 8.4 on PyPy; ruff and strict mypy pass.

Known spike limitations: shared behaviors with arguments, misuse of
placeholder values inside the describe body (no guard yet), transitive
parametrized dependencies of injected fixtures, docs and full coverage.
Fixtures declared as parameters of a describe block (or of a shared
behavior) are now injected into all tests nested in the block:

    def describe_create_book(user):
        def with_valid_book(valid_book):
            ...  # may use both the user and valid_book fixtures

Implementation:

- At collection, the describe function is called with named placeholder
  objects instead of failing, and the closure cells holding the
  placeholders are recorded on the DescribeBlock. The placeholders
  raise a descriptive error when used in the describe body itself.
- A pytest_generate_tests hook adds the declared names and their
  transitive dependencies to the fixture closure of each test in the
  block, so pytest resolves the fixtures itself and parametrized
  fixtures multiply the tests as usual.
- An autouse fixture, created lazily for each block with arguments,
  resolves the fixture values, writes them into the closure cells
  before other function-scoped fixtures of the block run, and restores
  the placeholders on teardown. This also allows fixtures defined in
  the block to use the describe arguments.
- Parameter handling mirrors pytest's fixture name detection
  (mandatory positional-or-keyword and keyword-only parameters).

Unlike the earlier proof of concept in PR #39, test functions are not
wrapped, so @pytest.mark.parametrize and signature introspection keep
working. Thanks to @ROpdebee for the original investigation.

Closes #38
@Cito Cito merged commit 0b8ea18 into main Jun 12, 2026
8 checks passed
@Cito Cito deleted the describe-fixture-args branch June 12, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fixtures as describe arguments

1 participant