Your next acceptance tests framework… bash ?

I have been trying several frameworks to test my pieces of software. The choice was difficult to make between cucumber-js, cucumber-java or karate.

All of them need a complex runtime. Either by installing a node_modules packages directory with a lot of dependencies, or by installing them in a .m2/repository directory.

I was wondering myself : Is it difficult to write a test that needs just an OS to run as pre-requisite ? I know that shell is a very powerful language, so I allowed myself to push the idea of testing my service only which a bash script. (at the end my team decided to use karate instead, but I think the idea is still worth the trial)

#!/usr/bin/env bash

SERVICE_NAME=my-service
API_URL_PREFIX=http://localhost:8080/${SERVICE_NAME}
MINIMUM_JAVA_VERSION="1.8.0"
HEALTH_COMMAND="curl $API_URL_PREFIX/health--connect-timeout 1 -s -o /dev/null"
PID=""

function ensureJavaVersionIsOk(){
local JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2)
if [ "$JAVA_VERSION" \< "$MINIMUM_JAVA_VERSION" ]; then
echo " expected java >= $MINIMUM_JAVA_VERSION but got $JAVA_VERSION"
failure
fi
echo " java version : $JAVA_VERSION"
}

function startService (){
local stepsize=1
local frames='⠁⠂⠄⡀⢀⠠⠐⠈'

if [ $(${HEALTH_COMMAND} ; echo $?) -eq 0 ];then
echo "$1 appears to be started already, and I cannot control the process" >&2
else
local jarfile=$(ls -S ../$1/target/ | head -n 1)
java -jar ../$1/target/${jarfile} >/dev/null 2>/dev/null &
local API=$(echo $!)

while [ $(${HEALTH_COMMAND} ; echo $?) -gt 0 ] && [ ! -z "$(ps | grep ${API})" ];do
s=$(( (s+$stepsize) %${#frames} ));
echo -ne "\r${frames:$s:$stepsize} starting $1" >&2
done

if [ $(${HEALTH_COMMAND} ; echo $?) -gt 0 ];then
echo ""
echo "💥 $SERVICE_NAME could not start"
failure
fi

PID=${API}
echo -e "\r$1 started (process id is $API)" >&2
fi
}

function killService(){
if [ ! -z "$2" ];then
echo ""
echo "👍 killing service $1"
kill -9 $2
fi
}

function success(){
killService ${SERVICE_NAME} ${PID}
echo ""
echo " Ok, thanks, bye"
exit 0
}

function failure(){
killService ${SERVICE_NAME} ${PID}
echo ""
echo " tests failed"
exit 1
}

function downloadJq(){
if [ ! -z "$(which jq)" ]; then
echo " jq is already installed"
elif [ ! -z "$(cat /proc/version | grep MINGW64)" ]; then
echo "→ downloading jq for windows"
curl $(curl https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe -v 2>&1 | grep Location: | cut -d' ' -f3) -o jq.exe -s
elif [ ! -z "$(cat /proc/version | grep Linux)" ]; then
echo "→ downloading jq locally in this env"
curl $(curl https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -v 2>&1 | grep Location: | cut -d' ' -f3) -o jq -s
chmod a+x ./jq
export PATH=PATH:$(pwd)
fi
}

function title(){
echo ""
echo -e "\033[1m $1 \033[0m"
}

function findValue(){
echo "$1" | jq -r $2
}

function assert() {
# First parameter is the message in case the assertion is not verified
message="$1"

# The remaining arguments make the command to execute
shift

# Run the command, $@ ensures arguments will remain in the same position.
# "$@" is equivalent to "$1" "$2" "$3" etc.
RESULT=$(eval "$@")

# Get the return code
rc=$?

# If everything is okay, there's nothing left to do
if [ ${rc} -eq 0 -a "$RESULT" != "null" ]; then
echo "⚛️ ${message}"
return 0
fi

# An error occured, retrieved the line and the name of the script where
# it happend
set $(caller)

# Get the date and time at which the assertion occured
date=$(date "+%Y-%m-%d %T%z")

# Output an error message on the standard error
# Format: date script [pid]: message (linenumber, return code)
echo " assertion failed : $message (line $1)" >&2

# Exit with the return code of the assertion test
failure
}



function getAgentToken(){
curl -s -X'POST' -E myfile.pem:password -d'user=1&group=2&agent=autoadmin' https://mycompany.com/oauth/token | jq -r '.token'
}

function getToken1(){
curl -s -X'POST' -E myfile.pem:password -d'username=johndoe&password=password' https://mycompany.com/oauth/tokens | jq -r '.token'
}

function getToken2(){
curl -s -X'POST' -E myfile.pem:password -d 'username=johnsmith&password=password' https://mycompany.com/oauth/tokens | jq -r '.token'
}

function callApi(){
echo "curl -s -H'Content-Type: application/json; charset=utf-8' -H 'Authorization: Bearer $1' $API_URL_PREFIX/$2 -d '$3'"
}

function firstTest(){
echo "$(eval "$(callApi ${TOKEN_1} test1 '{"data":"'$1'"}')")"
}

function secondTest(){
echo "$(eval "$(callApi ${TOKEN_2} test2 '{"data":"'$1'"}')")"
}

ensureJavaVersionIsOk
startService ${SERVICE_NAME}
downloadJq
TOKEN_1=$(getToken1)
TOKEN_2=$(getToken2)

title "Test first api"
DATA_1=$(firstTest myValue)
assert "first api response should have at least one item with an idea" [ ! -z "$(echo ${DATA_1} | jq ".[].id")" ]
assert "first item price should be 32 dollars" [ \"$(findValue "${DATA_1}" ".[].price_details.currency") $(findValue ${RELATED_PRODUCT_ITEM} ".[].price_details.total_price")\" = \"USD 32\" ]
DATA_1_ID="$(echo ${DATA_1} | jq -r ".[].id")"

title "Test second api"
DATA_2=$(secondTest ${DATA_1_ID})
assert "there is some data named JOHN" "[[ ! -z \"\$(echo \${DATA_2} | jq -r \".data[] | select(.name==\\\"JOHN\\\")\")\" ]]"
assert "there is some data named SMITH" "[[ ! -z \"\$(echo \${DATA_2} | jq -r \".data[] | select(.name==\\\"SMITH\\\")\")\" ]]"
success

It is possible to nest that execution during a maven / gradle build, just use a naive plugin like exec-maven-plugin (call your file blackbox-test.sh for example)

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>bash</executable>
<commandlineArgs>blackbox-test.sh</commandlineArgs>
</configuration>
</plugin>

There you are. A shell program that will start your api, execute some tests and run some assertions. It uses unix as dependency. This is lightweight and very focused. Have fun with it.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.