Skip to content

Custom asserts: a composed assertion should count and report once #917

Description

@Chemaclass

Background

docs/custom-asserts.md recommends composing custom assertions from built-in ones:

function assert_http_success() {
  local status_code="$1"

  assert_greater_or_equal_than "200" "$status_code"
  assert_less_than "300" "$status_code"
}

One call to assert_http_success counts as two assertions, and a failure prints the inner assertion's message rather than the custom one's.

Measured on main with this test file:

function test_composed_counts() {
  assert_http_success 200
}

function test_composed_fails() {
  assert_http_success 500
}
Tests:      1 passed, 1 failed, 2 total
Assertions: 3 passed, 1 failed, 4 total

Four assertions for two calls. The failure block reads Expected '500' to be less than '300', which describes the internal step rather than "not an HTTP success". The more built-ins a custom assertion composes, the further its reported totals drift from what the test author wrote.

Proposal

A nesting depth counter, so composed assertions count and report once.

  • Increment on entry to a public assertion, decrement on exit.
  • At depth greater than zero, a built-in returns its verdict but does not touch the counters and does not print.
  • The outermost frame reports a single assertion, with the custom assertion's own label and message.

bashunit::assert::should_skip (src/assert.sh:10) is the natural hook: all 71 assertions already call it first, so the depth bookkeeping lands in one place rather than in each assertion.

Notes

  • Bigger and riskier than the other custom-assert items, which is why it is filed on its own. It changes reported assertion totals for anyone already composing assertions, so it belongs in a minor release with a CHANGELOG note.
  • The depth counter has to be reset per test, not per file, and it must survive --parallel, where each test already runs in its own subshell.
  • Needs a decision on what the outermost failure prints when the custom assertion gives no label of its own. Falling back to the innermost message keeps today's behaviour and is probably the least surprising default.

Part of a custom-asserts DX set, each sized as its own PR:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions