Generated banner for Bats Testing Patterns
Generated logo for Bats Testing Patterns

Bats Testing Patterns

Seth HobsonCommunityCreated by Ryan SnodgrassDistributed by Seth Hobson

  • Skill
  • Guidelines
  • Software development
  • MIT
  • Low risk

From the author

Master Bash Automated Testing System (Bats) for comprehensive shell script testing. Use when writing tests for shell scripts, CI/CD pipelines, or requiring test-driven development of shell utilities.
— the description by Ryan Snodgrass

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

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/wshobson/bats-testing-patterns/download
  2. Unzip 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.tap
Makefile 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 reports

Best Practices

  1. Test one thing per test - Single responsibility principle

  2. Use descriptive test names - Clearly states what is being tested

  3. Clean up after tests - Always remove temporary files in teardown

  4. Test both success and failure paths - Don't just test happy path

  5. Mock external dependencies - Isolate unit under test

  6. Use fixtures for complex data - Makes tests more readable

  7. Run tests in CI/CD - Catch regressions early

  8. Test across shell dialects - Ensure portability

  9. Keep tests fast - Run in parallel when possible

  10. Document complex test setup - Explain unusual patterns

Files

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