r/bash • • 1d ago

Improve your Bash testing with Bats

I started solopreneuring a year ago, and my projects have a fair amount of Bash. My annoyances with Bats: I couldn't mock things efficiently, the assertion libraries are scattered over three repos with different naming, and table-driven tests are clumsy. I've written these scripts over the years in my career and thought about open-sourcing them, but never did. I'm using these libraries in my own frameworks and it helps me test my bash scripts in a more unified way, so I've chosen to productionize them and finally putting it out there:

bats-mock mocks and spies on commands and Bash functions. You set behavior with glob/regex rules, run your code, then assert on call counts, exact argv, stdin and call order across commands. Spies call through to the real thing. jasonkarns/bats-mock uses stubs on PATH, so it can't mock a function without unsetting it, and has no spies or stdin checks.

https://github.com/stealth-scale/bats-mock

bats-expect replaces bats-support, bats-assert and bats-file with one library. bats-assert's functions keep their names, options and failure format, so switching is a one-line change. Naming is consistent, and it adds assertions for variables, arrays, namerefs, numbers and JSON.

https://github.com/stealth-scale/bats-expect

bats-matrix does table-driven tests: inputs, expected exit status and expected output in one table, and a failure tells you which row broke and the exact command it ran. Bats' own bats_test_function works too, but needs a loop and a separate test body.

https://github.com/stealth-scale/bats-matrix

bats-test is the image I run it all in: every combination of Bash 4.4/5.1/5.2/5.3 and Bats 1.7.0/1.14.0, GNU tools, jq and a patched kcov.

https://github.com/stealth-scale/bats-test

I've been an engineer for a long time and I care about quality, so my libraries have a 100% line coverage gate in CI. That's how I ended up in a kcov rabbit hole: on Bash 5.3 one suite came out at 0%. kcov's Bash support had three bugs: a single quote inside [[ ]] made it drop every later hit, it counted every line of a multi-line command while Bash reports one, and its helper killed any bash -c child running with set -u. With the fixes, bats-mock went from 659 lines at 88% to 613 at 100%. The patches are in bats-test. I haven't sent them upstream yet, and I know I should.

If you don't mind switching frameworks, bashunit and ShellSpec already have mocks and data-driven tests. These repos are for staying on Bats.

The linked repo is a small release CLI whose test suite uses all four:

git clone --recurse-submodules https://github.com/stealth-scale/bats-example.git && cd bats-example && make test

It uses Podman (make test RUNTIME=docker for Docker) and runs without network. You need Bash 4.4+, and since the mocks are functions, /usr/bin/curl goes straight past them.

Everything is MIT (the image ships kcov, which is GPL-2.0). For me these tools are a big time-saver.

I'd love to hear what's still annoying to test in your Bash so I can further improve what I've used for years.

8 Upvotes

0 comments sorted by