# Testing Scripts with bats

A test is a small script that runs yours and checks the result; bats runs a file of them, each written with @test, run and checks on $status and $output, in a fresh folder from setup, and reports each one as passed or failed, with the line that failed.

Freelancer · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Write a bats test with @test, run and checks on $status and $output
- Run a script's tests with bats, and read which passed, which failed and why

## Key takeaways

- A test runs your script on known files and checks what happened, so one command rechecks everything after every change.
- In a .bats file, @test "name" { ... } is one test. run calls your script and saves its exit code in $status and its output in $output.
- Each check is an ordinary test such as [[ "$status" -eq 0 ]]. If any check fails, the test fails.
- setup runs before every test. cd "$BATS_TEST_TMPDIR" gives each test a fresh, empty folder of its own, so tests never touch your real files.
- bats file.bats prints a ✓ for each test that passed and a ✗ for each that failed, with the file's line number and the check that failed.

Every time you change `mover.sh`, you should check that it still moves photos, still refuses to replace one, and still explains itself when it's run wrong. By hand, that's a fresh practice folder and a dozen commands each time, and on a busy day you'll skip it. A cook doesn't skip tasting the soup, though, because tasting takes a second. **Tests** make checking a script that quick. A test is a small script that runs yours on known files and checks the result. You write it once, and then one command reruns every check. It's the same idea professional programmers use for every app they ship, and it's what each exercise in this course does when you run `bats check.bats`.

## Install bats

bats (the Bash Automated Testing System) runs tests written in bash. It's the tool that checks your exercises, so you installed it while setting up your terminal. If `bats --version` doesn't print a version number, install it now; `sudo` asks for your password:

```bash
sudo apt install bats       # Ubuntu, and Ubuntu in WSL
brew install bats-core      # a Mac, with Homebrew
```

## A test file: @test, run, $status and $output

Tests for `mover.sh` go in a file beside it, `mover.bats`, in the `mover` practice folder. Here are three:

```bash
#!/usr/bin/env bats
# mover.bats: tests for mover.sh. Run them with: bats mover.bats

setup() {
  mover="$BATS_TEST_DIRNAME/mover.sh"
  cd "$BATS_TEST_TMPDIR"
  mkdir inbox photos
  printf 'beach\n' > inbox/IMG_0412.jpg
}

@test "moves a photo into the photos folder" {
  run "$mover" inbox photos
  [[ "$status" -eq 0 ]]
  [[ "$output" == "Moved IMG_0412.jpg." ]]
  [[ -f photos/IMG_0412.jpg ]]
  [[ ! -e inbox/IMG_0412.jpg ]]
}

@test "never replaces a photo that's already there" {
  printf 'cake\n' > photos/IMG_0412.jpg
  run "$mover" inbox photos
  [[ "$status" -eq 1 ]]
  [[ "$(cat photos/IMG_0412.jpg)" == "cake" ]]
  [[ -f inbox/IMG_0412.jpg ]]
}

@test "with no folders, it shows how to use it" {
  run "$mover"
  [[ "$status" -eq 2 ]]
  [[ "$output" == "Usage: mover.sh FROM-FOLDER TO-FOLDER" ]]
}
```

Read it piece by piece:

- **`setup`** is a function bats runs before every test. `$BATS_TEST_TMPDIR` is a new, empty folder bats makes for each test and removes afterwards, so every test starts from the same fresh files and never touches your real photos. `$BATS_TEST_DIRNAME` is the folder the `.bats` file is in, where `mover.sh` lives.
- **`@test "name" { ... }`** is one test. The name says what should be true, in plain words, because it's what you'll read when it fails.
- **`run`** runs your script and saves what happened, instead of stopping the test: its exit code in **`$status`** and everything it printed, both streams together, in **`$output`**.
- **Each `[[ ]]`** is a check, also called an **assertion**: a statement that must be true. If any check in a test fails, the test fails.

## Run them: bats

From the practice folder, with `mover.sh` as *Debugging Scripts: bash -x* left it, bug fixed:

```bash
bats mover.bats
```

```text
mover.bats
 ✓ moves a photo into the photos folder
 ✓ never replaces a photo that's already there
 ✓ with no folders, it shows how to use it

3 tests, 0 failures
```

Three checks of real behavior in a moment, and safe to run as often as you like.

## When a test fails

Tests earn their keep on the day you break something. Put back the bug from *Debugging Scripts: bash -x* (`$file` instead of `$name` in the check) and run the tests again:

```text
mover.bats
 ✓ moves a photo into the photos folder
 ✗ never replaces a photo that's already there
   (in test file mover.bats, line 22)
     `[[ "$status" -eq 1 ]]' failed
 ✓ with no folders, it shows how to use it

3 tests, 1 failure
```

The ✗ names the test that failed, the line in `mover.bats`, and the check that wasn't true: the script ended with exit code 0 when a photo should have been skipped. You'd know about the lost photo before any real photo was lost. Fix the line, run `bats mover.bats` once more, and you're back to three ✓.

When you fix a bug, add a test that would have caught it. Then that bug can never come back without a ✗ telling you.

## Check yourself

2 questions to try on the lesson page.

## So what

A file of bats tests turns "I think it still works" into one command that proves it, every time you change the script.

## Key terms

- [bats](https://www.ask-codey.com/glossary/bats)
- [Unit Test](https://www.ask-codey.com/glossary/unit-test-unit-testing)
- [Assertion](https://www.ask-codey.com/glossary/assertion-unit-testing)
- [Test Runner](https://www.ask-codey.com/glossary/test-runner)

## Cheat sheets

- [Testing Scripts with bats](https://www.ask-codey.com/cheat-sheets/bash-testing-with-bats-cheat-sheet)

## Related

- [Debugging Scripts: bash -x](https://www.ask-codey.com/lessons/bash-debugging-scripts)
- [ShellCheck: A Second Pair of Eyes](https://www.ask-codey.com/lessons/bash-shellcheck)
- [Errors a User Can Act On](https://www.ask-codey.com/lessons/bash-script-errors)

Source: https://www.ask-codey.com/lessons/bash-testing-with-bats
