Skip to content

Custom asserts: a one-call assertion helper and self-labelled failures #915

Description

@Chemaclass

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:

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or request

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions