BATS (Bash Automated Testing System)

Source code: https://github.com/sarvsav/unit-testing-using-bats (Please ★ the repo, if you like it)

Post is dedicated to Brendan, who inspired me to write good tests.

Introduction

Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.

A Bats test file is a Bash script with special syntax for defining test cases. Under the hood, each test case is just a function with a description.

Usage

Bats can be used to test the dockerfiles and shell scripts. In this tutorial, we will cover how to write the unit tests for your shell scripts.

Installation in 3 easy steps

It will install the bats in $HOME/bin directory

git clone https://github.com/bats-core/bats-core.git
cd bats-core
./install.sh $HOME

On successful installation, you will see below message:

$ ./install.sh $HOME
Installed Bats to /c/Users/machine/bin/bats

To verify just run the command bats in your shell, and it will list it’s usage.

Structure

1. Add a `src` folder inside your root project that contains the shell script to test.

2. Add a `test` folder to write your bats test.

3. Add `main.bash` inside your `src` directory that contains the source code.

4. Add `test_helper.bash` insdie your `test` directory to write helper functions.

5. Add extenstions for `bats` by adding two git submodules using below commands to run from your project root folder.

git submodule add https://github.com/bats-core/bats-support.git test/test_helper/bats-support
git submodule add https://github.com/bats-core/bats-assert.git test/test_helper/bats-assert

Writing tests for code

#!/usr/bin/env bash## Author: codingtherightway.com
## File: src/main.bash
function greet() {
printf "Welcome, %s\n" "$1"
}
function bye() {
printf "Have a nice day, %s!\n" "$1"
}
function showuser() {
curl https://api.github.com/users/"$1" 2>/dev/null | grep "\"name\"" | awk -F: '{print $2}' | tr -d -- "\","
}
function main() {
# Should be replaced by getopts
case "$1" in
greet)
greet "$2"
;;
bye)
bye "$2"
;;
showuser)
showuser "$2"
;;
*)
echo "Sorry, I don't understand"
;;
esac
}
## A comparison operator can also be used, however the `-ef` gives more
## confidence.
## file1 -ef file2
## True if file1 and file2 refer to the same device and inode numbers.
## It will detect while we source the code and not run the code.

greet — To welcome user

bye — for saying bye

details — to display user github details using curl

Sample run:

$ ./main.bash showuser codingtherightway
Coding TheRightWay
$ ./main.bash greet codingtherightway
Welcome, codingtherightway
$ ./main.bash bye codingtherightway
Have a nice day, codingtherightway!

Add the code for helpers file: test/test_helper.bash

setup() {
# get the containing directory of this file
# use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0,
# as those will point to the bats executable's location or the preprocessed file respectively
DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
# make executables in src/ visible to PATH
PATH="$DIR/../src:$PATH"
}
teardown() {
echo "No action to perform"
}
existsAndExecutable() {
# Return true or false if file is executable or not
[[ -x "$1" ]]
}

The naming convention for test file and it’s functions is similar to writing tests for `golang`. So, the filename for our tests will be `test_main.bats`

First test case

To check whether script is executable or not.

#!/usr/bin/env batsload test_helperMAINSCRIPT="$BATS_TEST_DIRNAME/../src/main.bash"@test "TestMain: must be executable" {
existsAndExecutable "$MAINSCRIPT"
}
$ bats test/test_main.bats
✓ TestMain: must be executable
1 test, 0 failures

Adding test for greet function

We need to add following things to test our greet function.

1. Need to load the assert and support libraries

2. Source the main file

load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
MAINSCRIPT="$BATS_TEST_DIRNAME/../src/main.bash"
source "$MAINSCRIPT"
@test "TestGreet: print welcome message" {
run greet "codingtherightway"

assert_equal "$status" 0
assert_output "Welcome, codingtherightway"
}

It’s possible to mock the command in order to save the resources. For example, in showuser function we are using curl command and it can be mocked using bash

@test "TestShowuser_user: print user details" {
function curl() {
echo "\"name\":codingtherightway"
}
export -f curl

run showuser "codingtherightway"

assert_equal "$status" 0
assert_output "codingtherightway"
unset curl
}
$ bats test/test_main.bats
✓ TestMain: must be executable
✓ TestGreet_user: print welcome message
✓ TestShowuser_user: print user details
3 tests, 0 failures

References:

1. https://bats-core.readthedocs.io/en/stable/index.html

2. https://swalchemist.wordpress.com/2020/08/05/going-bats-with-bash-unit-testing/

3. https://jon.sprig.gs/blog/post/2316

--

--

--

Started writing code in GoLang. Enjoys working on Microservices and Distributes Systems. Project: learnyougo, inspired from learnyounode. github: sarvsav

Love podcasts or audiobooks? Learn on the go with our new app.

Python Vs R: The Ultimate Guide

python

Dynamic HTML Elements — An Approach to Flavors in Flutter Web

Speedment is a tool that uses code generation to produce a tailored domain model based on an…

Secure SSH and FTP servers by Port knocking

Meet the Most Comprehensive API Management Platform on the Market: ignite 6.0

Hyperion — A PostgreSQL tool to dynamically test your queries

Active Record associations in Rails

Spring Boot Security with JWT Authorization🔐

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Sarvsav Sharma

Sarvsav Sharma

Started writing code in GoLang. Enjoys working on Microservices and Distributes Systems. Project: learnyougo, inspired from learnyounode. github: sarvsav

More from Medium

What’s Docker?

In-Memory API Deployment

Optimizing Jenkins shared library loading on controllers

What is Kubernetes? Explain like I’m 5