pytest-just is a pytest plugin for testing justfile contracts.
It helps you catch recipe drift early:
- missing or renamed recipes
- broken dependency chains
- missing parameters
- missing variable threading
- accidental shebang usage in dry-run-safe recipes
pytest-just tests justfile contracts, not the underlying tools.
- It loads one structured snapshot via
just --dump --dump-format json. - It checks recipe shape/contracts (exists, deps, params, variables).
- It can use
just --showfor rendered body assertions. - It can use
just --dry-runfor safe smoke checks.
no_sync := "--no-sync"
lint:
uv run ruff check .
test:
uv run pytest -q
ci: lint test
@echo "ci checks passed"
deploy ENV:
@echo "deploying to {{ENV}}"
check:
uv run ruff check . {{no_sync}}import pytest
@pytest.mark.justfile
def test_contracts(just):
just.assert_exists("ci")
just.assert_depends_on("ci", ["lint", "test"])
just.assert_parameter("deploy", "ENV")
just.assert_variable_referenced("check", "no_sync")Short answer: a lot of value can be tested in isolation, but not everything.
Use pytest-just to validate recipe structure and intent:
- recipe exists
- dependency graph is correct
- required parameters are present
- expected command text is present
- variables are threaded through recipe bodies
- shebang vs non-shebang classification
These are fast, stable checks and catch the most common automation regressions.
For non-shebang recipes, you can validate expected command output via dry-run:
@pytest.mark.justfile
def test_test_recipe_dry_run(just):
result = just.dry_run("test")
assert result.returncode == 0, result.stderr
assert "uv run pytest" in (result.stdout + result.stderr)This is useful when you want to assert command composition without executing the real task.
If you need to verify side effects or real script output (for example, files created by ffmpeg, Docker behaviour, or external API calls), use separate integration tests that intentionally execute recipes in a controlled environment.
pytest-just is designed to test contracts and orchestration, not replace end-to-end execution tests.
@pytest.mark.justfile
def test_audio_intro_es_contract(just):
just.assert_exists("audio-intro-es")
just.assert_body_contains("audio-intro-es", "PIPER_MODEL must be set")
just.assert_body_contains("audio-intro-es", "generate_intro_audio_piper.py")
assert just.is_shebang("audio-intro-es")The example above verifies the recipe contract clearly, without invoking the underlying audio tooling.
This project uses uv for dependency management.
uv sync --extra devuv run pytest -qTo target justfile-focused tests:
uv run pytest -m justfile -qpytest-just exposes two CLI options:
--justfile-root: directory containingjustfileorJustfile--just-bin: executable path/name forjust(default:just)
Example:
uv run pytest -q --justfile-root examples/public/actix-web --just-bin justIf --justfile-root is not provided, the plugin walks upward from the pytest root directory until it finds justfile or Justfile.
The plugin registers a session-scoped fixture named just:
def test_recipe_exists(just):
just.assert_exists("test")Session scope means the JSON dump is loaded once per test run.
Useful accessors on JustfileFixture:
recipe_names(include_private=False)dependencies(recipe)parameters(recipe)parameter_names(recipe)is_private(recipe)is_shebang(recipe)doc(recipe)body(recipe)show(recipe)assignments()aliases()
These methods are designed for concise tests:
assert_exists(recipe)assert_depends_on(recipe, expected, transitive=False)assert_parameter(recipe, parameter)assert_body_contains(recipe, text)assert_not_shebang(recipe)assert_variable_referenced(recipe, variable)assert_dry_run_contains(recipe, text, *args, env=None)
Use dry_run to verify command shape safely:
def test_check_recipe_is_dry_run_safe(just):
result = just.dry_run("check")
assert result.returncode == 0, result.stderrdry_run rejects shebang recipes because they can still execute interpreters even in dry-run mode.
Sample justfiles are included under:
examples/public/async-compressionexamples/public/actix-webexamples/public/martin
These are useful for:
- regression tests
- API behaviour checks
- parser compatibility checks across real-world syntax patterns
uv sync --extra dev
uv run ruff check .
uv run ty check
uv run pytest -qIn addition to example-driven tests, pytest-just uses Hypothesis to generate varied inputs and verify invariants.
Current property checks cover:
- root discovery across arbitrarily nested directories
- idempotence of recipe body normalisation
- recipe signature stability when dependency/parameter order changes
- alias and assignment extraction contracts for valid dump payloads
Run only property tests:
uv run pytest -q tests/test_hypothesis_properties.pyShow generated-case statistics:
uv run pytest -q --hypothesis-show-statisticsIf a property test fails, Hypothesis will shrink the failing input to a minimal reproducible example.
Install just and/or pass --just-bin.
Ensure your just version supports JSON dump (>= 1.13) and the target justfile parses cleanly.
Check whether the recipe is private and whether your assertion is using the expected name (not alias).