Repository navigation
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
Problem
bashunit::mock (src/doubles/mock.sh:45) replaces a command with a fixed body or a fixed exit code, and that is all it can do. Two everyday testing needs have no expression:
1. Different results per call. Retry logic, polling loops and backoff are exactly the code that most needs testing, and they all require "fail the first time, succeed the second":
# there is no way to write this today
bashunit::mock curl --returns-in-sequence 1 1 0The workaround is a hand-rolled counter file in each test, which every user reinvents.
2. Asserting a command was never called. The spy side can assert "called N times", but expressing "this destructive command must never run" means writing assert_have_been_called_times 0 rm, which reads backwards and — before #895 — silently passed for a command that was never spied at all.
Proposal
bashunit::mock_sequence <cmd> <body-or-code> [<body-or-code>…]- Each call consumes the next entry; the last entry repeats once exhausted (document this — the alternative, failing after exhaustion, is also defensible; pick one and test it).
- Entries follow the existing convention that an all-digits value is an exit code and anything else is a body (
bashunit::doubles::is_exit_code,src/doubles/mock.sh:37), so the two doubles keep reading the same way. - Registered in the same registry the runner unwinds after each test, so no leakage between tests.
assert_have_never_been_called <cmd>- Fails when the command was called at least once, showing the recorded call log (
bashunit::spy::call_log_to_slot,src/doubles/spy.sh:92). - Fails when
<cmd>was never spied, matching the fix(doubles): call assertions silently pass when the command was never spied #895 rule that an unregistered double is a broken test, not a vacuous pass.
Acceptance criteria
-
bashunit::mock_sequence cmd 1 0returns 1 on the first call and 0 on the second - Behaviour after the sequence is exhausted is documented and tested
- Bodies and exit codes can be mixed in one sequence
- The sequence state resets between tests (no leakage), including under
--parallel -
bashunit::unmockclears a sequenced mock completely - A spy over a sequenced mock still records every call
-
assert_have_never_been_called cmdpasses when the command was spied and never called - It fails, printing the call log, when the command was called
- It fails when
cmdwas never spied (fix(doubles): call assertions silently pass when the command was never spied #895 rule) - The generated mock body stays free of injectable shell syntax (the reason
is_exit_codeexists — see the comment atsrc/doubles/mock.sh:33) - Bash 3.0 compatible; no per-assertion fork regression
- Documented in
docs/test-doubles.md; tests intests/unit/doubles/andtests/functional/doubles_test.sh
Repo checklist (agent)
- TDD: RED → GREEN → REFACTOR. Write the failing test first.
- Bash 3.0+ only: no
printf -v, no+=append, nodeclare -A, no[[ ]], no${var,,}, no&>>, no${arr[-1]}. Expanding a possibly-empty array underset -uneeds${arr[@]+"${arr[@]}"}. - A new CLI flag must be wired in all of these or a parity test fails:
- parse in
src/main/test.sh(report-style flags needexport -n, seesrc/main/test.sh:188-196for why) - validate the value in
bashunit::main::validate_config_or_exit(src/main/validate.sh:60) — unvalidated input used to run the wrong thing and exit 0 (Unknown options are silently ignored: a typo'd flag runs a different suite and exits 0 #871, --jobs with a non-integer value hangs on Bash 3.x and is silently ignored on Bash 4.3+ #873) - default + env var in
src/config/env.shand a documented line in.env.example --helptext in the same block it belongs to- both
completions/bashunit.bashandcompletions/_bashunit(anti-drift test feat(cli): bash and zsh completion scripts with an anti-drift test #778 fails otherwise)
- parse in
- Gates:
make sa,make lint,./bashunit tests/,./bashunit --parallel tests/. Never runshfmt -w. - Docs: update
docs/command-line.md. Editingdocs/assertions.mdinvalidates thebashunit docacceptance snapshot — regenerate it. - CHANGELOG.md: add one line under
## Unreleased. - Fixtures under
tests/acceptance/fixtures/must not end in*test.sh. - One issue = one PR.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Type
Projects
- StatusShow more project fieldsDone
Activity
Chemaclass commentedon Aug 12, 2026
Done in #1070 (merged).