Just Module Tests¶
Contract and resolution tests for the just module infrastructure. Run via just maint test contract test (or
just maint test contract test -v -k compose for filtered output).
Test Files¶
- test_spec.py — verifies WHAT recipes promise (contracts), not HOW they work
- test_modules.py — verifies modules are REACHABLE (CLI resolution), not what they do
- test_gen_spec.py — unit tests for the spec generator (tools/gen_spec.py)
- conftest.py —
JustfileFixture(from pytest-just) and parametrized fixtures - fixtures/spec.yaml — per-recipe expectations, generated via
just maint test spec spec-gen - tools/gen_spec.py — standalone tool to bootstrap spec.yaml from the live dump
How spec.yaml drives tests¶
At import time, conftest.py reads fixtures/spec.yaml and builds three parametrized fixtures from it. Each fixture
yields a different slice of the spec:
| Fixture | Yields | Tests |
|---|---|---|
spec_case |
(namepath, expectations_dict) |
Privacy, dependencies, parameters |
spec_namepaths |
set[str] |
Spec/dump completeness (same recipes) |
flag_case |
(namepath, param_name, attrs_dict) |
Parameter attributes match |
At test time, each test receives:
jf— theJustfileFixture(session-scoped, autouse), which flattens the livejust --dumpoutput into a flat{namepath: recipe_data}dict- A parametrized value from one of the fixtures above
The test calls jf.is_private(), jf.dependencies(), jf.parameters(), etc. to check the live dump against the spec's
declared expectations.
The spec owns the expectations, the dump owns the truth. The test asks: Does truth match expectation?
Spec format¶
Grouped by contract — recipes with identical properties (private, depends, params) share a single entry. Each group
lists namepaths first, then contract keys alphabetically.
- namepaths:
- docker::compose::down
- docker::compose::restart
- docker::compose::up
depends:
- _require-docker
- _require-compose
params:
interactive:
long: interactive
short: i
value: 'true'
profile:
long: profile
short: p
services: {}
How test_modules.py iterates (fixture-based parametrization)¶
test_modules.py tests CLI resolution — listing, cross-directory, invocation path equivalence. Unlike
@pytest.mark.parametrize (which declares values at the test site), these tests use fixture-based parametrization:
the fixture is declared in conftest.py with params=, and any test requesting it by name runs once per value
automatically.
This means the iteration is invisible at the test site — a test like this runs 16+ times because module is a fixture
with params=_jf.module_namepaths.
def test_module_lists(self, module: str) -> None:
result = just("--list", module, "--unsorted")
assert result.returncode == 0
Dump-driven fixtures (no hardcoded module names)¶
| Fixture | Yields | Source |
|---|---|---|
module |
each module path | _jf.module_namepaths |
top_module |
top-level modules (no ::) |
modules without :: in path |
parent_child_module |
(parent, child) pairs |
modules where parent::child exists |
module_recipe |
(module, recipe) pairs |
all non-common public recipes per module |
call_var |
(module, var) pairs |
_just_call_* variables from dump |
All data is discovered from the live just --dump at import time. Adding a new module or recipe automatically adds it
to the test matrix — no test file changes needed.
Regenerating the spec¶
After adding or modifying recipes:
just maint test spec spec-gen # overwrites fixtures/spec.yaml from the live dump
just maint test contract test # verify everything passes
Upstream issues¶
Features that would simplify or improve this test infrastructure:
- DataBooth/pytest-just#5 — Module support in
JustfileFixture. Upstreams the module flattening used by these tests. - DataBooth/pytest-just#2 —
assert_parameterattribute checking. - DataBooth/pytest-just#3 —
dry_runfor all recipe types.