Step runner
When working with mocks, you would have to setup a mock for each command call with the expected argument numbers, return value, possible output and an index of the call. Then, you would run the code to be tested and run assertions for each of the mocked commands. For large scripts maintaining both parts becomes a tedious task.
This helper allows to setup and process a sequence of string and mocked command assertions. It helps to make maintenance of complex tests easier.
Consider this example:
# Declare STEPS as a global variable, as `process_steps` needs to be called
# twice and it does not store the steps internally.
declare -a STEPS=(
# Mock `drush` binary with an exit status of 1 and no output.
"@drush -y status --field=drupal-version # 1"
# Mock `drush` binary with an exit status of 0 and output "success".
"@drush -y status --fields=bootstrap # success"
# Mock `drush` binary with an exit status of 1 and output "failure".
"@drush -y status --fields=bootstrap # 1 # failure"
# Mock `drush` binary with side effect that creates a file.
"@drush cache-rebuild # 0 # Cache rebuilt # touch /tmp/cache-cleared"
# Mock any git command with wildcard arguments
"@git * # 0 # Git operation successful"
# Assert presence of the partial string in the output "Hello world"
"Hello world"
# Assert absence of the partial string in the output "Goodbye world"
"- Goodbye world"
)
# Setup phase.
mocks="$(process_steps "setup")" # $mocks will hold created mocks
# ... code to be tested ...
# Assert phase.
process_steps "assert" "$mocks" # Assertions will be performed.
Step types
A step can be one of the following types:
Command
@<command> [<args>] # <mock_status> [ # <mock_output> [ # <mock_side_effect> ]]
Mock the command <command>
with the given status, optional output, and optional side effect.
Status can be omitted and <mock_output>
can be used instead.
Side effect is Bash code that will be executed when the mock is called.
Steps for the same @command
can be mocked multiple times.
Call to the same command will use the same mock.
Wildcard Arguments: Use *
as <args>
to accept any arguments for the command:
@<command> * # <mock_status> [ # <mock_output> [ # <mock_side_effect> ]]
Substring presence:
<substring>
Assert that the output contains the given substring.
Substring absence
- <substring>
Assert that the output does not contain the specified substring.
Starts with -
(minus followed by a space).
Wildcard Arguments
Wildcard arguments (*
) allow you to mock any invocation of a command regardless of the arguments passed to it. This is useful when:
- You don't care about the specific arguments
- The arguments vary but you want consistent mock behavior
- You want to mock multiple calls to the same command with different arguments
Wildcard Examples
declare -a STEPS=(
# Mock any curl command - accepts any arguments
"@curl * # 0 # Mock response data"
# Mock git commands with any arguments
"@git * # 0 # Git operation completed"
# Mix specific and wildcard mocks - specific ones take precedence
"@npm install express # 0 # Installing express"
"@npm * # 0 # Generic npm operation"
# Wildcard with side effects
"@backup * # 0 # Backup completed # touch \${BATS_TEST_TMPDIR}/backup.done"
)
Note: When both specific argument patterns and wildcards are defined for the same command, the specific patterns take precedence over wildcards.
Side Effects
Side effects allow you to execute arbitrary Bash code when a mock is called. This is useful for:
- Creating files or directories that your code under test expects to exist
- Setting environment variables
- Logging mock calls for debugging
- Simulating complex command behaviors
Side Effect Examples
declare -a STEPS=(
# Create a temporary file when the command is called
"@backup_database # 0 # Backup completed # touch \${BATS_TEST_TMPDIR}/backup.sql"
# Set an environment variable
"@load_config # 0 # Config loaded # export APP_ENV=test"
# Multiple side effect commands
"@deploy # 0 # Deployed successfully # mkdir -p /tmp/deploy; echo 'deployed' > /tmp/deploy/status"
# Side effect with no output
"@cleanup # 0 # # rm -rf \${BATS_TEST_TMPDIR}/temp_files"
# Side effect with error status
"@failing_command # 1 # Command failed # echo 'Error logged' > \${BATS_TEST_TMPDIR}/error.log"
)
Important Notes
- Side effects are executed in the context of the mock, not the test
- Use
${BATS_TEST_TMPDIR}
for temporary files in tests - Side effects run after output is generated but before the exit status is returned
- Multiple commands can be chained using
;
or&&
- Each step can have its own side effect - different invocations of the same command can have different side effects