Background
Writing a custom assertion today is a three-part contract, spelled out under "Best practices" in docs/custom-asserts.md:
function assert_positive_number() {
local actual="$1"
if [ "$actual" -le 0 ]; then
bashunit::assertion_failed "positive number" "$actual"
return
fi
bashunit::assertion_passed
}
There are two ways to get that wrong, and the framework catches neither:
- Forget the
return after bashunit::assertion_failed, and both the failed and the passed counter bump for a single assertion.
- Forget
bashunit::assertion_passed, and the test registers zero assertions, so it silently lands as risky (src/runner.sh:1411) or as a failure under --fail-on-risky.
Separately, a custom assertion cannot name itself in its own failure block. bashunit::assert::fail_with (src/assert.sh:52) already takes a label as $1, but the public bashunit::assertion_failed (src/bashunit.sh:7) hard-codes it to "", so the output always shows the test name.
Proposal
1. bashunit::assert_that <expected> <actual> <cmd> [args...]
Runs the command and marks the assertion passed or failed accordingly. No eval, no return dance, Bash 3.0 safe:
function bashunit::assert_that() {
bashunit::assert::should_skip && return 0
local expected=$1
local actual=$2
shift 2
if "$@"; then
bashunit::state::add_assertions_passed
return 0
fi
bashunit::assert::fail_with "" "$expected" "but got " "$actual"
return 1
}
The example above collapses to one line, with both counters impossible to desync:
function assert_positive_number() {
bashunit::assert_that "positive number" "$1" test "$1" -gt 0
}
2. Optional label on bashunit::assertion_failed
Accept a fourth argument and forward it into fail_with's existing $1, so a custom assertion can label its own failure. Backward compatible.
3. Docs
Update docs/custom-asserts.md with the new form, and mention --boot / BASHUNIT_BOOTSTRAP as the way to load a shared custom-assert file once. Every current example sources it from set_up, which is per test file.
Notes
Entirely additive. The existing three-part form keeps working, so no deprecation is needed.
Part of a custom-asserts DX set, each sized as its own PR:
Background
Writing a custom assertion today is a three-part contract, spelled out under "Best practices" in docs/custom-asserts.md:
There are two ways to get that wrong, and the framework catches neither:
returnafterbashunit::assertion_failed, and both the failed and the passed counter bump for a single assertion.bashunit::assertion_passed, and the test registers zero assertions, so it silently lands as risky (src/runner.sh:1411) or as a failure under--fail-on-risky.Separately, a custom assertion cannot name itself in its own failure block.
bashunit::assert::fail_with(src/assert.sh:52) already takes a label as$1, but the publicbashunit::assertion_failed(src/bashunit.sh:7) hard-codes it to"", so the output always shows the test name.Proposal
1.
bashunit::assert_that <expected> <actual> <cmd> [args...]Runs the command and marks the assertion passed or failed accordingly. No
eval, no return dance, Bash 3.0 safe:The example above collapses to one line, with both counters impossible to desync:
2. Optional label on
bashunit::assertion_failedAccept a fourth argument and forward it into
fail_with's existing$1, so a custom assertion can label its own failure. Backward compatible.3. Docs
Update
docs/custom-asserts.mdwith the new form, and mention--boot/BASHUNIT_BOOTSTRAPas the way to load a shared custom-assert file once. Every current example sources it fromset_up, which is per test file.Notes
Entirely additive. The existing three-part form keeps working, so no deprecation is needed.
Part of a custom-asserts DX set, each sized as its own PR:
bashunit doc#918 listing project assertions inbashunit doc