bashautomationbats-core

bats - how can i echo the file name in a bats script for reporting


I have some bats scripts that I run to test some functionality how can I echo the bats file name in the script?

my bats script looks like:

#!/usr/bin/env bats
load test_helper
echo $BATS_TEST_FILENAME


@test "run cloned mission" {
blah blah blah
}

in order for my report to appear as:

 ✓ run cloned mission
 ✓ run cloned mission
 ✓ addition using bc
---- TEST NAME IS xxx
 ✓ run cloned mission
 ✓ run cloned mission
 ✓ addition using bc
---- TEST NAME IS yyy
 ✓ run cloned mission
 ✓ run cloned mission
 ✓ addition using bc

but got the error

2: syntax error:
operand expected (error token is ".bats
2")

what is the correct way to do it? I don't want to change the sets names for it only to echo the filename between different tests.

Thanks.


Solution

  • TL;DR

    Just output the file name from the setup function using a combination of prefixing the message with # and redirecting it to fd3 (documented in the project README).

    #!/usr/bin/env bats
    
    setup() {
        if [ "${BATS_TEST_NUMBER}" = 1 ];then
            echo "# --- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" >&3
        fi
    }
    
    @test "run cloned mission" {
        blah blah blah
    }
    

    All your options

    Just use BASH

    The simplest solution is to just iterate all test files and output the filename yourself:

    for file in $(find ./ -name '*.bats');do
        echo "--- TEST NAME IS ${file}"
        bats "${file}"
    done
    

    The downside of this solution is that you lose the summary at the end. Instead a summary will be given after each single file.

    Use the setup function

    The simplest solution within BATS is to output the file name from a setup function. I think this is the solution you are after.

    The code looks like this:

    setup() {
        if [ "${BATS_TEST_NUMBER}" = 1 ];then
            echo "# --- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" >&3
        fi
    }
    

    A few things to note:

    Use a skipped @test

    The next solution would be to just add the following as the first test in each file:

    @test "--- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" {
        skip ''
    }
    

    The downside here is that there will be an addition to the amount of skipped tests...

    Use an external helper function

    The only other solution I can think of would be to create a test helper that lives in global scope and keeps tracks of its state.

    Such code would look something like this:

    output-test-name-helper.bash

    #!/usr/bin/env bash
    
    create_tmp_file() {
        local -r fileName="$(basename ${BATS_TEST_FILENAME})"
    
        if [[ ! -f "${BATS_TMPDIR}/${fileName}" ]];then
            touch "${BATS_TMPDIR}/${fileName}"
            echo "---- TEST NAME IS ${fileName}" >&2
        fi
    }
    
    remove_tmp_file() {
        rm "${BATS_TMPDIR}/$(basename ${BATS_TEST_FILENAME})"
    }
    
    trap remove_tmp_file EXIT
    
    create_tmp_file
    

    Which could then be loaded in each test:

    #!/usr/bin/env bats
    
    load output-test-name-helper
    
    @test "run cloned mission" {
        return 0
    }
    

    The major downside here is that there are no guarantees where the output is most likely to end up.

    Adding output from outside the @test, setup and teardown functions can lead to unexpected results.

    Such code will also be called (at least) once for every test, slowing down execution.

    Open a pull-request

    As a last resort, you could patch the code of BATS yourself, open a pull-request on the BATS repository and hope this functionality will be supported natively by BATS.

    Conclusion

    Life is a bunch of tradeoffs. Pick a solution that most closely fits your needs.