---
title: "Let's assert and stub for bash scripts"
url: 'https://blog.philippludewig.de/de/writing/lets-assert-and-stub-for-bash-scripts'
markdown: 'https://blog.philippludewig.de/de/writing/lets-assert-and-stub-for-bash-scripts.md'
lang: de
date: '2023-08-13'
description: "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 comm…"
taxonomy:
  category:
    - blog
    - '2023'
  tag:
    - bash
    - bats-core
---

# Let's assert and stub for bash scripts

Aloha people,

In my last post about [bats-core](https://blog.philippludewig.de/de/writing/lets-test-bash-scripts), 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](https://github.com/jasonkarns/bats-mock). How to stub a command like date looked so much easier than from [grayhemp](https://github.com/grayhemp/bats-mock). 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 ](https://stackoverflow.com/questions/38315185/how-to-use-bats-mock-to-assert-against-calls-to-a-mocked-script-in-bash-testin) 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](https://github.com/DiesDasJenes/bats-mock-learnings/blob/main/test/how-to-mock-v1.bats) and another from the user [endreymarcell](https://github.com/DiesDasJenes/bats-mock-learnings/blob/main/test/how-to-mock-v2.bats). I prefer the solution from endreymarcell as it's simpler and more readable.

```bash
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](https://github.com/endreymarcell/spoon/blob/master/test/bats-setup.sh).

Let's talk about stubbing. Bats-mock has three methods to stub the behaviour of a mock [ `mock_set_status`](https://github.com/grayhemp/bats-mock#mock_set_status),[ `mock_set_output`,](https://github.com/grayhemp/bats-mock#mock_set_side_effect) and [ `mock_set_side_effect`](https://github.com/grayhemp/bats-mock#mock_set_output). 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](https://github.com/DiesDasJenes/bats-mock-learnings/blob/main/test/stubbing.bats). 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..

```bash
@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](https://github.com/DiesDasJenes/bats-mock-learnings/blob/main/test/verify.bats):

```bash
@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](https://github.com/DiesDasJenes/bats-mock-learnings/tree/main).

<script id="diffblog-plugin-script" async="false" src="https://diff.blog/static/js/diffblog_plugin_v1.js"></script> 

<script> document.getElementById("diffblog-plugin-script").addEventListener("load", function () { DiffBlog( "rifghkd0g7ya9ovtjt66t9tw7himfb6dduwshidacju84nf7g3" ); }); </script> 

<script src="https://cdn.commento.io/js/commento.js"></script>

---

## Navigation

- Parent: [Life, Tech & Adventures: A Journey in Bytes and Boardgames](https://blog.philippludewig.de/de/index.md)
- Previous: [Five Dysfunctions of a team](https://blog.philippludewig.de/de/writing/five-dysfunctions-of-a-team.md)
- Next: [Bash Script Testing: A Journey to Bats-Core](https://blog.philippludewig.de/de/writing/lets-test-bash-scripts.md)
