Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Deprecated forms now warn at runtime on stderr; silence with `BASHUNIT_NO_DEPRECATION_WARNINGS=true` (#866)
- `bashunit::state::print_line` / `print_tap_line` moved to `bashunit::console_results::*`; no alias kept (#868)
- `bashunit doc` and the [Assertions](https://bashunit.com/assertions) page now cover all 71 assertions, with a quick-reference table
- Docs: `bashunit::unmock` and the lifecycle of a double — automatic per-test cleanup, `set_up_before_script` doubles, and what `unmock` reaches (#899)
- Docs: invalid-input handling, `BASHUNIT_REPORT_TAP`/`BASHUNIT_REPORT_JSON`, the standalone exit code, and the optional `["snapshot_file"]` argument
- The docs sidebar outline now lists `h3` headings
- The `--parallel` unsupported-OS warning no longer claims Alpine is excluded
Expand Down
4 changes: 4 additions & 0 deletions docs/ai-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ actually make against this API:
- Use `$(bashunit::temp_file)` / `$(bashunit::temp_dir)` for scratch files — they are
cleaned up automatically and are safe under `--parallel`.
- Never call the network in a test. Use `bashunit::mock` / `bashunit::spy` instead.
- Doubles are torn down after every test — do not add `bashunit::unmock` to `tear_down`.
Use it inside a test to get the real command back, or to suspend a
`set_up_before_script` double for that one test (it cannot undo it for the others,
since each test runs in its own subshell).
- Spy assertion argument order is inconsistent — check, don't guess:
`assert_have_been_called_times <count> <spy>` but
`assert_have_been_called_with <spy> <expected> [call_index]`.
Expand Down
6 changes: 6 additions & 0 deletions docs/public/bashunit-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ bashunit::mock date echo "2024-05-01" # replace behaviour
bashunit::mock uname <<< "Linux" # heredoc form ignores the call's arguments
bashunit::mock curl 1 # all-digits arg = exit code, no output (as for spy)
bashunit::spy send_email # record calls, keep behaviour
bashunit::unmock date # restore the real command for the rest of this test

assert_have_been_called send_email
assert_have_been_called_times 2 send_email # count FIRST, then spy
Expand All @@ -214,6 +215,11 @@ The argument order is inconsistent between `_times` and `_with`. A swapped pair
the assertion* rather than erroring, so it reads like a real defect — check this list
rather than guessing.

A double declared inside a test is removed when that test ends, so never unmock in
`tear_down` — it is noise. One declared in `set_up_before_script` lives for the whole
file; `bashunit::unmock` on it only suspends it for the current test, because each test
runs in its own subshell.

## Data providers

Each line of the provider becomes one run, split on whitespace into `$1`, `$2`, …:
Expand Down
29 changes: 29 additions & 0 deletions docs/test-doubles.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,35 @@ test_example() {
```
:::

## bashunit::unmock
> `bashunit::unmock "function"`

Removes a double: it drops the function override — restoring the real command — and deletes the state files a spy recorded its calls in. It is a no-op for a name that was never mocked or spied.

::: code-group
```bash [Example]
function test_example() {
bashunit::mock ls echo "mocked"
assert_same "mocked" "$(ls)"

bashunit::unmock ls

assert_not_same "mocked" "$(ls)"
}
```
:::

### Lifecycle of a double

You rarely need to call it, because doubles are cleaned up for you:

- A double created **inside a test** is removed after that test. There is no need to unmock in `tear_down`, and nothing leaks into the next test.
- A double created in **`set_up_before_script`** stays in place for every test in the file — that is the point of declaring it there.

Each test runs in its own subshell, so `bashunit::unmock` only affects the test that calls it. Calling it on a `set_up_before_script` double suspends that double for the current test; the following tests still see it.

Reach for it when a single test needs the real command back after exercising the mocked path, or when it needs a spy's recorded calls to start over mid-test. Re-declaring a double does not require it: a second `bashunit::mock` or `bashunit::spy` on the same command replaces the first.

## bashunit::spy
> `bashunit::spy "function"`

Expand Down
58 changes: 58 additions & 0 deletions tests/functional/doubles_lifecycle_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/bin/bash

# Pins the lifecycle documented in docs/test-doubles.md: doubles declared in
# set_up_before_script live for the whole file, doubles declared inside a test
# die with it, and bashunit::unmock reaches no further than the current test.

function set_up_before_script() {
bashunit::mock lifecycle_shared_double echo "from before script"
}

function test_a_before_script_double_is_visible_in_a_test() {
assert_same "from before script" "$(lifecycle_shared_double)"
}

function test_b_unmock_only_affects_the_current_test() {
bashunit::unmock lifecycle_shared_double

assert_empty "$(lifecycle_shared_double 2>/dev/null || true)"
}

function test_c_before_script_double_survives_the_unmock_of_another_test() {
assert_same "from before script" "$(lifecycle_shared_double)"
}

function test_d_a_double_declared_in_a_test_does_not_leak() {
bashunit::mock lifecycle_local_double echo "local"

assert_same "local" "$(lifecycle_local_double)"
}

function test_e_the_previous_test_double_is_gone() {
assert_empty "$(lifecycle_local_double 2>/dev/null || true)"
}

function test_unmock_restores_the_real_command() {
bashunit::mock ls echo "mocked"
assert_same "mocked" "$(ls)"

bashunit::unmock ls

assert_not_same "mocked" "$(ls)"
}

function test_unmock_forgets_the_calls_recorded_so_far() {
bashunit::spy lifecycle_spy
lifecycle_spy once
bashunit::unmock lifecycle_spy

bashunit::spy lifecycle_spy

assert_empty "$(assert_not_called lifecycle_spy 2>&1)"
}

function test_unmock_of_an_unknown_name_is_a_no_op() {
bashunit::unmock lifecycle_never_mocked

assert_successful_code "$?"
}
Loading