Aloha people,
In my last post about bats-core, I described how you can start testing bash scripts. Today I want to share some of my learnings regarding stubbing with Bats-Mock. I won't lie, when I saw the two repositories for mocking. I went straight with Jason Karns repository. How to stub a command like date looked so much easier than from grayhemp. The problem with that: it doesn't work. The last change that was made to Jason Karns repository was eight years ago. In the meantime, bats-core must have changed in a way that made this repository unusable. I wasn't able to stub or unstub properly, and spent hours figuring out if I could make it work. I reached a point where I looked for help in the only place a developer would go: Stack Overflow. The side did not disappoint, as I found what I needed. The user dragon788 wrote a comprehensive explanation about both repositories and helped me to understand grayhemps bats-mock. I whole heartily can say, ditch Jason Karns repository and just use grayhemps.
The README of grayhemps bats-mock contains an example in which the scripts expect an environment variable to be set that contains a path to the executable. I haven't seen a script that does that to be honest. Therefore, I was a little confused about how I should add the mocks to the PATH. I found two different solutions to this and one from the user dragon788 and another from the user endreymarcell. I prefer the solution from endreymarcell as it's simpler and more readable.
setup() {
load /test_frameworks/test_helper/bats-support/load
load /test_frameworks/test_helper/bats-assert/load
load /test_frameworks/test_helper/bats-mock/load
export mock_date_path
mock_date_path="$(mock_create)"
export -f date
}
teardown() {
export -n date
}
date() {
bash "${mock_date_path}" "${@}"
}
@test 'Stub date so it returns a specific date' {
mock_set_output "${mock_date_path}" "1460967598.184561556"
run date
assert_output "1460967598.184561556"
}
In this example I have stubbed date
through exporting a function with the same name that points to the mock_date_path
. I have done this directly in the test file but we could also create a setup script that does this for us as we likely reruse the date
function in other scripts. Here is an example from the spoon repository.
Let's talk about stubbing. Bats-mock has three methods to stub the behaviour of a mock
mock_set_status
, mock_set_output
, and
mock_set_side_effect
. These functions can also be used to stub the behaviour of several calls to the same mock by adding a counter. The counter starts at 1. The function mock_set_output
lets you set the output that the mock would produce, and mock_set_side_effect
enables you to do something when the mock was called. An example for this could be to copy a test file to a specific folder. The example can be found here. I observed that the mocks act more like a spy and don't overwrite the shell commands, although I had added them at the start of the PATH. I had the case where the shell command unzip
would still expect a file and broke the test. Furthermore, I had expected that the mock would be chosen over the actual executable. After I supplied a test file the shell command could unzip, I was able to verify the args to the unzip
mock successfully..
@test 'Stub date so it behaves differently on three calls' {
# Just wanna show we can inject values into the side effect string
temp_dir=$(mktemp -d)
mock_set_output "${mock_date_path}" "111" 1
mock_set_side_effect "${mock_date_path}" "touch $temp_dir/someFile" 2
mock_set_output "${mock_date_path}" "222" 3
run date
assert_output "111"
run date
assert_file_exists "$temp_dir/someFile"
run date
assert_output "222"
rm "$temp_dir/someFile"
}
Now that we have stubbed some mocks, it's time to verify the calls to those mocks. For this bats-mock has four functions mock_get_call_num
, mock_get_call_user
, mock_get_call_args
and mock_get_call_env
. In order to use the functions, you will need to combine them with assert_equal
from bats-core. Similar to the stubbing, it is also possible to verify specific calls to a mock with a counter. Here is the example file:
@test 'Verify that stub date is called with the argument' {
mock_set_output "${mock_date_path}" "1460967598.184561556"
date --utc
assert_equal "$(mock_get_call_args ${mock_date_path} 1)" "--utc"
}
I hope this will help people have an easier start with bats-mock. Check out the repository about my bats-mock learnings here.