bashshellunit-testingmockingbats-core

Is it possible to mock "command" when testing in bash?


One of my scripts is trying to figure out if all it's dependencies are installed when there is an error. In order to do that I use the built-in command. Let's assume the most limited version I could come up with (let's call that script_to_test.sh:

command -v pdfgrep
return $?

Now I have a bats-core test file test.sh:

setup() {
  # See https://bats-core.readthedocs.io/en/stable/tutorial.html
  load 'test_helper/bats-support/load'
  load 'test_helper/bats-assert/load'
  # See https://github.com/buildkite-plugins/bats-mock
  load 'test_helper/mocks/stub'
  DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
  PATH="$DIR/..:$PATH"
}

@test "Test mocking built-ins" {
  stub command \
    "-v pdfgrep : exit 17"
  run script_to_test.sh
  unstub command
  assert_failure 17
}

When I run that, I get:

`unstub command' failed

This is because the stubbed command was never called.
Is it even possible to stub built-ins? How can I test my scripts behavior where it depends on the environment?


Solution

  • As I know, it is not possible, because built-in commands are part of the shell itself, and they cannot be easily replaced or stubbed! but maybe I can explain it to you with another approach using an example, in my scenario instead of relying on the built-in command directly, you can create a wrapper function or script that encapsulates the behavior of the built-in command and after that, you can stub or mock this wrapper function/script in your tests, for example when you run test.sh using Bats, it will execute the test case named Test mocking built-ins. so,I stubbed the check_dependency function to simulate the scenario where pdfgrep is not installed and the test expects the script to fail with an exit code of 1!

    you can move the check_dependency function into its own separate file, let's say dependency_checker.sh,and then source this file in both script_to_test.sh and test.sh so that they can access the function.

    let me show you how:

    dependency_checker.sh:

    #!/bin/bash
    
    check_dependency() {
      command -v "$1" >/dev/null 2>&1
      return $?
    }
    

    script_to_test.sh:

    #!/bin/bash
    
    source dependency_checker.sh
    
    check_dependency pdfgrep
    return $?
    

    test.sh:

    setup() {
      load 'test_helper/bats-support/load'
      load 'test_helper/bats-assert/load'
      load 'test_helper/mocks/stub'
      DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"
      PATH="$DIR/..:$PATH"
    }
    
    @test "Test mocking built-ins" {
      stub check_dependency \
        "pdfgrep : return 1"
      run script_to_test.sh
      unstub check_dependency
      assert_failure 1
    }