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

220 Views Asked by At

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?

2

There are 2 best solutions below

2
Freeman On BEST ANSWER

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
}
0
Lii On

Another way to kind of achieve this:

  1. Create mock versions of the commands that you want to mock as Bash scripts.
  2. Update PATH when running the test so that the mock scripts are picked up instead of the real ones.

Doing this manually is probably too much hassle. Shellmock is apparently a framework that automates this.