# Bash Testing Rules Applies to: `**/*.bats` Implements the core principles from `testing.md`. All rules there apply here — this file covers shell-specific patterns. ## Framework: bats-core Use [bats-core](https://bats-core.readthedocs.io/) for shell tests. A test file is `.bats`; each test is a `@test "description" { ... }` block; a non-zero exit inside the block fails the test. Run a file with `bats path/to/file.bats`, or a tree with `bats -r tests/`. Drive the script under test with `run`: it captures `$status` (exit code), `$output` (combined stdout+stderr), and `$lines[]` (output split by line) without the failure aborting the test. Assert on those. ```bash @test "greet: prints the name passed in" { run bash "$SCRIPT" --name Ada [ "$status" -eq 0 ] [[ "$output" == *"Hello, Ada"* ]] } ``` ## Test the Real Script, Through Its Interface Run the actual script file — never copy its logic into the test. Invoke it the way a caller does (`run bash "$SCRIPT" `, or `run "$SCRIPT"` when it's executable) and assert on exit status and output. A test that re-implements the script's logic passes even when the script breaks. For a script that sources a library of functions, source the library in `setup` and call the functions directly — that's the unit level; the `run` invocation is the integration level. ## Normal, Boundary, Error — the Three Categories Cover all three from `testing.md` per script: - Normal: the expected arguments and inputs produce the expected output and a zero exit. - Boundary: empty argument, missing optional flag, single-item vs many, whitespace and unicode in inputs, a path with a space. - Error: missing required argument, nonexistent input file, a dependency absent. Assert the exit code and that the error names the problem — not the exact wording (`testing.md`'s error-behavior rule). ## Isolation and Determinism - `setup()` makes a fresh `mktemp -d` per test; `teardown()` removes it. No test leans on another's leftovers, and tests pass in any order. - Mock an external command by putting a stub earlier on `PATH`: write a small script named like the command into a temp dir, `chmod +x`, and prepend that dir to `PATH` for the `run`. This is how you simulate a tool being absent, returning an error, or emitting canned output — without touching the network or the real tool. - Never hardcode dates; generate them relative to `date` (see the `task-review-staleness.bats` pattern in this repo for relative-date fixtures). - Mock at the boundary (network, the external CLI, the clock). Don't mock the script's own functions — those are the work. ## What Not to Do - Don't assert exact error-message prose; assert the exit code plus a value the message must contain. - Don't share mutable state between tests through a fixed temp path. - Don't test that `shellcheck` or `bats` themselves work — trust the tools. - Don't skip the error cases because the happy path passes; the error paths are where shell scripts actually break.