From b283c40177ff66b552e64ea7205d7d54d7ba8e55 Mon Sep 17 00:00:00 2001 From: SauronBot Date: Sun, 29 Mar 2026 21:31:48 +0200 Subject: [PATCH 1/3] feat(spy): support optional exit code or custom implementation in bashunit::spy Closes #600 bashunit::spy now accepts an optional second argument: - Integer: the spy returns that exit code (e.g. `bashunit::spy thing 1`) - Function name: the spy delegates to that implementation after recording the call (e.g. `bashunit::spy thing mock_thing`) Default behaviour (no second argument) is unchanged: the spy is a no-op returning exit code 0. --- src/test_doubles.sh | 9 ++++++ tests/unit/test_doubles_test.sh | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/test_doubles.sh b/src/test_doubles.sh index df4f60a3..c1d6d253 100644 --- a/src/test_doubles.sh +++ b/src/test_doubles.sh @@ -44,6 +44,7 @@ function bashunit::mock() { function bashunit::spy() { local command=$1 + local exit_code_or_impl="${2:-}" local variable variable="$(bashunit::helper::normalize_variable_name "$command")" @@ -56,6 +57,13 @@ function bashunit::spy() { export "${variable}_times_file"="$times_file" export "${variable}_params_file"="$params_file" + local body_suffix="" + if [[ "$exit_code_or_impl" =~ ^[0-9]+$ ]]; then + body_suffix="return $exit_code_or_impl" + elif [ -n "$exit_code_or_impl" ]; then + body_suffix="$exit_code_or_impl \"\$@\"" + fi + eval "function $command() { local raw=\"\$*\" local serialized=\"\" @@ -69,6 +77,7 @@ function bashunit::spy() { _c=\$(cat '$times_file' 2>/dev/null || builtin echo 0) _c=\$((_c+1)) builtin echo \"\$_c\" > '$times_file' + $body_suffix }" export -f "${command?}" diff --git a/tests/unit/test_doubles_test.sh b/tests/unit/test_doubles_test.sh index 9a6e6530..247c8e9b 100644 --- a/tests/unit/test_doubles_test.sh +++ b/tests/unit/test_doubles_test.sh @@ -216,3 +216,54 @@ function test_unsuccessful_spy_nth_called_with_invalid_index() { } + +function test_spy_with_exit_code_returns_specified_exit_code() { + bashunit::spy ps 1 + + ps + local actual_exit_code=$? + + assert_have_been_called ps + assert_same "1" "$actual_exit_code" +} + +function test_spy_with_exit_code_zero_returns_zero() { + bashunit::spy ps 0 + + ps + local actual_exit_code=$? + + assert_have_been_called ps + assert_same "0" "$actual_exit_code" +} + +function test_spy_with_impl_calls_custom_function() { + custom_ps_impl() { + builtin echo "custom output" + } + export -f custom_ps_impl + + bashunit::spy ps custom_ps_impl + + local output + output=$(ps) + + assert_have_been_called ps + assert_same "custom output" "$output" +} + +function test_spy_with_impl_records_calls_and_delegates() { + custom_ps_impl() { + builtin echo "delegated" + } + export -f custom_ps_impl + + bashunit::spy ps custom_ps_impl + + ps first + ps second + + assert_have_been_called_times 2 ps + assert_have_been_called_nth_with 1 ps "first" + assert_have_been_called_nth_with 2 ps "second" +} From 919e726bda78aee5889de7641ce79c13ad78475f Mon Sep 17 00:00:00 2001 From: SauronBot Date: Sun, 29 Mar 2026 21:31:57 +0200 Subject: [PATCH 2/3] docs(changelog): add entry for feat #600 spy exit code and custom impl --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d24d347f..3a657701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Added +- Allow `bashunit::spy` to accept an optional exit code (e.g. `bashunit::spy thing 1`) or custom implementation function (e.g. `bashunit::spy thing mock_thing`) (#600) + ### Fixed - Fix spying on `echo` or `printf` causing bashunit to hang due to infinite recursion (#607) From 7da0649721f7a62f0e5b9a0b763c5224913f07a5 Mon Sep 17 00:00:00 2001 From: SauronBot Date: Sun, 29 Mar 2026 21:40:37 +0200 Subject: [PATCH 3/3] fix(test): capture exit code safely under strict mode in spy exit code test Using 'ps || actual_exit_code=$?' prevents set -e from aborting the test when the spy returns a non-zero exit code. --- tests/unit/test_doubles_test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_doubles_test.sh b/tests/unit/test_doubles_test.sh index 247c8e9b..85a90d5f 100644 --- a/tests/unit/test_doubles_test.sh +++ b/tests/unit/test_doubles_test.sh @@ -220,8 +220,8 @@ function test_unsuccessful_spy_nth_called_with_invalid_index() { function test_spy_with_exit_code_returns_specified_exit_code() { bashunit::spy ps 1 - ps - local actual_exit_code=$? + local actual_exit_code=0 + ps || actual_exit_code=$? assert_have_been_called ps assert_same "1" "$actual_exit_code"