Bats Testing Patterns
Seth HobsonCommunityCreated by Ryan SnodgrassDistributed by Seth Hobson
- Skill
- Guidelines
- Software development
- MIT
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to testing shell scripts using Bats. It covers fundamental testing patterns, including setup and teardown procedures, assertion techniques for exit codes and file states, and strategies for handling dependencies. The documentation includes best practices for maintaining testable shell code, integrating tests into CI/CD pipelines, and ensuring cross-shell compatibility.
What it does
- Defines patterns for unit testing shell scripts with Bats.
- Provides templates for setup, teardown, and test fixtures.
- Demonstrates assertions for exit codes, output strings, and file system states.
- Shows how to mock dependencies and handle shell environment variations.
- Offers guidance on integrating Bats into CI/CD workflows and Makefiles.
- Outlines best practices for writing maintainable and portable shell tests.
When to use it
- When you need to write unit tests for shell scripts.
- When implementing test-driven development for command-line utilities.
- When setting up automated testing for CI/CD pipelines.
- When validating script behavior across different shell dialects.
- When you need to test edge cases, error conditions, or complex file operations.
Advantages
- Enables consistent, automated validation of shell scripts.
- Supports TAP-compliant output for easy integration with CI systems.
- Promotes modular test design through reusable helpers and fixtures.
- Facilitates testing of error paths and edge cases that are hard to verify manually.
- Helps ensure script portability across bash, sh, and dash.
Usage examples
- “How do I test that my shell script fails when a file is missing?”
- The skill provides a pattern using the 'run' command to capture the exit status and verify it is non-zero, while also checking the output for specific error messages.
- “How can I run my Bats tests in a GitHub Actions workflow?”
- The skill provides a YAML configuration snippet for a GitHub Actions job that installs Bats and executes the test suite.
- “What is the best way to clean up temporary files after a test?”
- The skill demonstrates the use of the 'teardown()' function to ensure temporary directories and files are removed after every test execution.
- “How do I test if my script works in both bash and dash?”
- The skill shows how to write individual @test blocks that explicitly invoke the script with different shell interpreters.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/wshobson/bats-testing-patterns/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip bats-testing-patterns.zip -d .agents/skills
Documentation by Ryan Snodgrass
Bats Testing Patterns
Comprehensive guidance for writing comprehensive unit tests for shell scripts using Bats (Bash Automated Testing System), including test patterns, fixtures, and best practices for production-grade shell testing.
When to Use This Skill
Writing unit tests for shell scripts
Implementing test-driven development (TDD) for scripts
Setting up automated testing in CI/CD pipelines
Testing edge cases and error conditions
Validating behavior across different shell environments
Building maintainable test suites for scripts
Creating fixtures for complex test scenarios
Testing multiple shell dialects (bash, sh, dash)
Detailed patterns and worked examples
Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.
Testing Error Conditions
#!/usr/bin/env bats
@test "Function fails with missing file" {
run my_function "/nonexistent/file.txt"
[ "$status" -ne 0 ]
[[ "$output" == *"not found"* ]]
}
@test "Function fails with invalid input" {
run my_function ""
[ "$status" -ne 0 ]
}
@test "Function fails with permission denied" {
touch "$TMPDIR/readonly.txt"
chmod 000 "$TMPDIR/readonly.txt"
run my_function "$TMPDIR/readonly.txt"
[ "$status" -ne 0 ]
chmod 644 "$TMPDIR/readonly.txt" # Cleanup
}
@test "Function provides helpful error message" {
run my_function --invalid-option
[ "$status" -ne 0 ]
[[ "$output" == *"Usage:"* ]]
}Testing with Dependencies
#!/usr/bin/env bats
setup() {
# Check for required tools
if ! command -v jq &>/dev/null; then
skip "jq is not installed"
fi
export SCRIPT="${BATS_TEST_DIRNAME}/../bin/script.sh"
}
@test "JSON parsing works" {
skip_if ! command -v jq &>/dev/null
run my_json_parser '{"key": "value"}'
[ "$status" -eq 0 ]
}Testing Shell Compatibility
#!/usr/bin/env bats
@test "Script works in bash" {
bash "${BATS_TEST_DIRNAME}/../bin/script.sh" arg1
}
@test "Script works in sh (POSIX)" {
sh "${BATS_TEST_DIRNAME}/../bin/script.sh" arg1
}
@test "Script works in dash" {
if command -v dash &>/dev/null; then
dash "${BATS_TEST_DIRNAME}/../bin/script.sh" arg1
else
skip "dash not installed"
fi
}Parallel Execution
#!/usr/bin/env bats
@test "Multiple independent operations" {
run bash -c 'for i in {1..10}; do
my_operation "$i" &
done
wait'
[ "$status" -eq 0 ]
}
@test "Concurrent file operations" {
for i in {1..5}; do
my_function "$TMPDIR/file$i" &
done
wait
[ -f "$TMPDIR/file1" ]
[ -f "$TMPDIR/file5" ]
}Test Helper Pattern
test_helper.sh
#!/usr/bin/env bash
# Source script under test
export SCRIPT_DIR="${BATS_TEST_DIRNAME%/*}/bin"
# Common test utilities
assert_file_exists() {
if [ ! -f "$1" ]; then
echo "Expected file to exist: $1"
return 1
fi
}
assert_file_equals() {
local file="$1"
local expected="$2"
if [ ! -f "$file" ]; then
echo "File does not exist: $file"
return 1
fi
local actual=$(cat "$file")
if [ "$actual" != "$expected" ]; then
echo "File contents do not match"
echo "Expected: $expected"
echo "Actual: $actual"
return 1
fi
}
# Create temporary test directory
setup_test_dir() {
export TEST_DIR=$(mktemp -d)
}
cleanup_test_dir() {
rm -rf "$TEST_DIR"
}Integration with CI/CD
GitHub Actions Workflow
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Bats
run: |
npm install --global bats
- name: Run Tests
run: |
bats tests/*.bats
- name: Run Tests with Tap Reporter
run: |
bats tests/*.bats --tap | tee test_output.tapMakefile Integration
.PHONY: test test-verbose test-tap
test:
bats tests/*.bats
test-verbose:
bats tests/*.bats --verbose
test-tap:
bats tests/*.bats --tap
test-parallel:
bats tests/*.bats --parallel 4
coverage: test
# Optional: Generate coverage reportsBest Practices
Test one thing per test - Single responsibility principle
Use descriptive test names - Clearly states what is being tested
Clean up after tests - Always remove temporary files in teardown
Test both success and failure paths - Don't just test happy path
Mock external dependencies - Isolate unit under test
Use fixtures for complex data - Makes tests more readable
Run tests in CI/CD - Catch regressions early
Test across shell dialects - Ensure portability
Keep tests fast - Run in parallel when possible
Document complex test setup - Explain unusual patterns
Files
- ATTRIBUTION.mdAdded by AutoGineer892 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md5.1 KiB
- references/details.md7.1 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Ryan Snodgrass
- Publisher
- Seth Hobson (Community)
- Distributed by
- Seth Hobson
- Source
- wshobson/agents · plugins/shell-scripting/skills/bats-testing-patterns · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Ryan Snodgrass or Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download bats-testing-patterns.zip