Created
July 2, 2021 14:05
-
-
Save noel-yap/5f985d89696788b3aca8a9c3c0b43c7e to your computer and use it in GitHub Desktop.
bash mocking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setup() { | |
. "${BATS_TEST_DIRNAME}/bash-inject.shlib" | |
} | |
@test "should default to defining function" { | |
assert_equal "$(declare -F fn)" '' | |
@inject "${BATS_TEST_DIRNAME}/fn.sh" | |
run echo "$(declare -f fn)" | |
assert_output -p "${BATS_TEST_DIRNAME}/fn.sh" | |
} | |
@test "should not define recursive function" { | |
assert_equal "$(declare -F fn)" '' | |
@inject "fn" | |
run echo "$(declare -f fn)" | |
assert_output '' | |
} | |
@test "should use injected function" { | |
assert_equal "$(declare -F fn)" '' | |
function fn() { | |
echo mock fn | |
} | |
@inject "${BATS_TEST_DIRNAME}/fn.sh" | |
run echo "$(declare -f fn)" | |
assert_output -p 'echo mock fn' | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# shellcheck disable=SC2148 | |
# @inject allows an executable call to be mocked out by defining a function that calls the executable by default. | |
# A pre-defined function of the same name will take precedence thereby allowing mocking. | |
# Example: | |
# In sut-script.sh: | |
# @inject /path/to/dependency-executable.sh | |
# | |
# dependency-executable dependency-executable-args | |
# | |
# In sut-script.bats: | |
# function dependency-executable() { | |
# assert_equal "$1" 'arg1' | |
# } | |
# | |
# run sut-script sut-script-args | |
function @inject() { | |
executable="$1" | |
shift $# | |
fn="$(basename "${executable}" | sed -e 's|\..*||')" | |
quot='"' | |
declare -F "${fn}" >/dev/null || | |
[ "${fn}" == "${executable}" ] || | |
eval "function ${fn}() { ${executable} ${quot}\$@${quot}; }" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setup() { | |
. "${BATS_TEST_DIRNAME}/bash-mock.shlib" | |
} | |
@test "should use actual dependency" { | |
run "${BATS_TEST_DIRNAME}/testdata/sut-script.sh" | |
assert_success | |
assert_line 'first-call' | |
assert_line 'second-call' | |
} | |
@test "should use mock" { | |
@mock dependency-executable | |
function dependency-executable@0() { | |
assert_equal "$1" 'first-call' | |
echo 'mocked first-call' | |
} | |
function dependency-executable@1() { | |
assert_equal "$1" 'second-call' | |
echo 'mocked second-call' | |
} | |
run "${BATS_TEST_DIRNAME}/testdata/sut-script.sh" | |
assert_success | |
assert_line 'mocked first-call' | |
assert_line 'mocked second-call' | |
} | |
@test "should work when mock is called in subshells" { | |
@mock dependency-executable | |
function dependency-executable@0() { | |
assert_equal "$1" 'first-call' | |
echo 'mocked first-call' | |
} | |
function dependency-executable@1() { | |
assert_equal "$1" 'second-call' | |
echo 'mocked second-call' | |
} | |
run "${BATS_TEST_DIRNAME}/testdata/sut-script-with-subshell-calls.sh" | |
assert_success | |
assert_line 'mocked first-call' | |
assert_line 'mocked second-call' | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# shellcheck disable=SC2148 | |
# @mock creates a base mock function that delegates to other mock implementations. | |
# Example: | |
# In sut-script.sh: | |
# @inject dependency-executable | |
# | |
# dependency-executable dependency-executable-args | |
# | |
# In sut-script.bats: | |
# @mock dependency-executable | |
# # will be called on sut-script.sh's first call to dependency-executable | |
# function dependency-executable@0() { | |
# assert_equal "$1" 'first-call' | |
# } | |
# # will be called on sut-script.sh's second call to dependency-executable | |
# function dependency-executable@1() { | |
# assert_equal "$1" 'second-call' | |
# } | |
# | |
# run sut-script sut-script-args | |
# TODO(nyap): support each mock implementation being called a specified number of times; currently, each implementation is called only once | |
function @mock() { | |
tmpdir="$(mktemp -d --tmpdir="${BATS_TMPDIR}")" | |
name="$1" | |
counter_name="${name/-/_}_counter" | |
counter_filename="${tmpdir}/${counter_name}" | |
echo "${counter_name}=0" >"${counter_filename}" | |
quot='"' | |
# since the mock can be called from sub-shells, the counter state is stored in a file | |
eval "function ${name}() { | |
. ${counter_filename}; | |
echo ${counter_name}=\$((${counter_name} + 1)) >${counter_filename}; | |
${name}@\${${counter_name}} ${quot}\$@${quot}; | |
}" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment