Search code examples
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:

    • The output MUST begin with a hash #
    • The MUST be a space after the hash
    • The output MUST be redirected to file descriptor 3 (i.e. >&3)
    • A check is added to only output the file name once (for the first test) The downside here is that the output might confuse people as it shows up in red.

    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.