Test files
bashunit discovers, names, and runs test files through a set of conventions and helpers. In this section, you'll find information about these features along with some helpful tips.
Test file names
bashunit is flexible about how you name your test files.
You can use a directory name, and bashunit will look for all files (ending with test.sh or test.bash) recursively inside that directory, and execute them.
If you're using wildcards for scanning your tests, keep in mind that the initial search can slow down if you don't filter the test files in the wildcard.
To optimize this, we recommend adding a test prefix or suffix to your test file names, and include this identifier in your wildcard pattern too (e.g., **/*test.sh or **/*test.bash). This naming convention not only speeds up the scanning process but also helps you keep your test files organized.
This is useful regardless of whether your test files are located near your production code or share directories with your mocks, stubs, or fixtures.
Test function names
bashunit will search for and execute all test functions it finds within each test file. To distinguish test functions from auxiliary functions, the name must start with test_ — lowercase, and the underscore is part of the prefix. Everything after it is yours. Below are some example test function names that would work seamlessly:
function test_should_validate_an_ok_exit_code() { ... }
function test_getFunctionsToRun_with_filter_should_return_matching_functions { ... }
test_render_all_tests_passed_when_not_failed_tests() { ... }TIP
You're free to use any of Bash's syntax options to define these functions: function name(), name(), or function name without parentheses. The syntax is never what decides whether a function runs — only the name is.
WARNING
test_ is matched literally, so testRenderAllTests (no underscore) and TEST_upper (uppercase) are auxiliary functions, not tests. bashunit does not warn about them: if the file also holds a real test, the run is green and simply contains fewer tests than you wrote. Check with --list when a test you expected never appears in the output.
This is also what lets a helper named testdata_path stay a helper.
Custom test titles
By default, bashunit derives the name shown in reports from the test function name. If you need a more descriptive title, you can override it inside the test using bashunit::set_test_title:
function test_handles_invalid_input() {
bashunit::set_test_title "🔥 handles custom test names 🚀"
# test logic...
}The provided title is used only for display purposes. The original function name is still used internally, and custom titles are reset automatically after each test.
Per-test annotations
--test-timeout and --retry are run-wide, but the need is usually per test: one integration test needs 30 seconds while the other 400 stay strict. Write the annotation in the comment block directly above the function, the same place # @tag goes:
# @tag integration
# @timeout 30
# @retry 3
function test_slow_integration() {
assert_successful_code "$(sync_with_the_api)"
}
# @skip needs a live database
function test_reads_from_the_database() {
# never executed, not even set_up
assert_not_empty "$(query "select 1")"
}↷ Skipped: Reads from the database
needs a live database| Annotation | Effect |
|---|---|
@timeout <seconds> | Overrides --test-timeout for this test, in both directions. 0 disables the timeout for this test even when the run sets one. |
@retry <n> | Overrides --retry for this test. |
@skip [reason] | Reports the test as skipped with the reason; the body and the hooks never run. |
The association follows the # @tag rule: other comment lines keep the block open, a blank line breaks it, and both function test_x and test_x() definition styles are recognised. A value that is not a non-negative integer (# @timeout abc, # @retry -1) aborts the run with an error rather than silently falling back to the default — the run would otherwise not be the one the annotation asked for.
set_up function
The set_up auxiliary function is called, if it is present in the test file, before each test function in the test file is executed. This provides a hook to prepare the environment or set initial variables specific to each test case. For example, you might want to create temporary directories or files that your test will manipulate.
function set_up() {
touch temp_file.txt
}tear_down function
The tear_down auxiliary function is called, if it is present in the test file, immediately after each test function in the test file is executed. This auxiliary function offers you a place to clean up any resources allocated or changes made during the set_up or test function itself. This helps to ensure that each test starts with a fresh state.
function tear_down() {
rm temp_file.txt
}set_up_before_script function
The set_up_before_script auxiliary function is called, if it is present in the test file, only once before all tests functions in the test file begin. This is useful for global setup that applies to all test functions in the script, such as loading shared resources.
During test execution, bashunit displays the hook execution with its duration, right-aligned to match test output:
Running tests/example_test.sh
● set_up_before_script 2.03s
✓ Passed: test_example 12msThis visibility helps identify slow setup operations that may impact test run time.
A failing command or non-zero function status makes set_up_before_script fail. bashunit reports the hook error, marks every test in the file as failed (they are included in the totals), and continues with the next test file. Watch out for a trailing cmd && var=value guard: when cmd fails, the guard is the hook's return value. The rest of the suite always runs, and the failure is attributed to the hook rather than surfacing as mysterious individual test errors. tear_down_after_script still runs, so it can release resources acquired before the setup failure. Because setup may be only partially complete, guard optional state in teardown, for example [ -n "${RESOURCE:-}" ] && rm -f "$RESOURCE".
If you want a missing optional dependency to skip tests instead of failing them, end the hook with an explicit success, for example command -v jq >/dev/null 2>&1 && HAS_JQ=true; return 0, then call bashunit::skip inside the tests.
function set_up_before_script() {
open_database_connection
}tear_down_after_script function
The tear_down_after_script auxiliary function is called, if it is present in the test file, once after the file finishes. It also runs when set_up_before_script fails, even though the test functions cannot run. This auxiliary function is similar to how set_up_before_script works but at the end of the tests. It provides a hook for any cleanup that should occur after all tests have run, such as deleting temporary files or releasing resources.
Like set_up_before_script, the execution is displayed with its duration:
✓ Passed: test_example 12ms
● tear_down_after_script 1.05s
Tests: 1 passed, 1 totalFailures inside tear_down_after_script are also surfaced as dedicated errors after the final test output so cleanup problems (for example, missing tools or permissions) are visible in the run summary.
function tear_down_after_script() {
close_database_connection
}Syntax errors in test files
If a test file contains a Bash syntax error, bashunit records a failing test for that file instead of silently skipping the remaining tests. The exact error message from Bash (including file path and line number) is shown in the summary, and the suite exits with a non-zero status.
Running tests/example_test.sh
✗ Error: Source
tests/example_test.sh: line 10: syntax error near unexpected token `fi'
tests/example_test.sh: line 10: ` fi'
Tests: 1 failed, 1 total
Some tests failedThis guarantees a broken test file always fails the suite, so it never passes by absence.
Related
- Command line — discover and run test files from the terminal
- Configuration — set the default test path and bootstrap file
- Assertions — assertions to use inside your test functions
- Common patterns — real-world testing patterns