A full-featured BDD unit testing framework for shell scripts.
Let’s test your shell script! (Try the Online Demo on the browser).
ShellSpec is a full-featured BDD unit testing framework for dash, bash, ksh, zsh and all POSIX shells that provides first-class features such as code coverage, mocking, parallel execution, parameterized testing and more. It was developed as a dev/test tool for cross-platform shell scripts and shell script libraries. Most of features are implemented with pure shell script and minimal POSIX-compliant commands, so they work also in restricted environments such as tiny Docker images and embedded systems.
- Works with all POSIX compliant shells (dash, bash, zsh, ksh, busybox, etc...)
- Minimal dependencies (use only a few basic POSIX-compliant commands)
- BDD style specfile compatible with shell script syntax (can embed shell script)
- Structured test using nestable blocks with scoped (isolation between tests)
- Easy and powerfull mocking in cooperation with block scope
- Easy to Skip/Pending of the examples
- Before/After and BeforeAll/BeforeAll hooks
- Parameterized examples for Data-Driven tests
- Execution filtering by line number, id, focus, tag and example name
- Quick execution to run examples that not-passed (or failed) the last time it ran
- Execution with trace output for debugging
- Parallel execution, random ordered execution and dry-run execution
- Modern reporting (colorized, failed line number, progress / documentation / TAP / JUnit formatter)
- Code coverage (Kcov integration) and Profiler
- Friendly with CI and provides Docker images with ShellSpec pre-installed
- Built-in project directory generator and simple task runner
- Extensible architecture (custom assertion, custom matcher, etc...)
- Subproject: ShellMetrics - Cyclomatic Complexity Analyzer for shell scripts
See CHANGELOG.md
- Requirements
- Installation
- Tutorial
- shellspec command
- Usage (
--help) - Initialize your project (
--init) - Specify the shell to run (
--shell) - Quick execution (
--quick,--repair,--next) - Parallel execution (
--jobs) - Random execution (
--random) - Fail fast (
--fail-fast) - Trace (
--xtrace,--xtrace-only) - Sandbox mode (
--sandbox) - Ranges (
:LINENO,:@ID) / Filters (--example) / Focus (--focus) - Reporter (
--format) / Generator (--output) - Coverage (
--kcov) - Profiler (
--profile) - Task runner (
--task)
- Usage (
- Special files/directories in the project directory
- Typical project directory structure
.shellspec/.shellspec-local- configure default options.shellspec-quick.log- log file for quick executionreport/- output directory of report filecoverage/- output directory of coverage reportsspec/- default specfiles directoryspec/banner- banner file displayed at test executionspec/spec_helper.sh- default helper file for specfilespec/support/- directory for support filesspec/support/bin- directory for support commands
- Specfile
- Example
- Basic structure
- Helpers
- Hooks
- Directives
- Mock and Stub
- Support commands
- Testing a single file script
- Self-executable specfile
- For developers
bash>=2.03,bosh/pbosh>=2018/10/07,posh>=0.3.14,yash>=2.29,zsh>=3.1.9dash>=0.5.2,busybox ash>=1.10.2,busybox-w32,GWSH>=20190627ksh88,ksh93>=93s,ksh2020,mksh/lksh>=R28,pdksh>=5.2.14FreeBSD sh,NetBSD sh,OpenBSD ksh,loksh,oksh
| Platform | Test |
|---|---|
| Linux (Debian, Ubuntu, Fedora, CentOS, Alpine, Busybox, OpenWrt) | Travis CI or Docker or manual |
| macOS (Default installed shells, Homebrew) | Travis CI or manual |
| Windows (Git bash, msys2, cygwin, busybox-w32, WSL) | Cirrus CI or manual |
| BSD (FreeBSD, OpenBSD, NetBSD) | Cirrus CI or manual |
| Unix (Solaris, AIX) | manual only |
ShellSpec uses shell built-in commands and only few basic POSIX-compliant commands to
support widely environments (except kcov for optional code coverage).
Currently used external (not shell builtins) commands:
cat,date,env,ls,mkdir,od(or not POSIXhexdump),rm,sleep,sort,timeps(use to auto-detect shells in environments that don't implement procfs)ln,mv(use only when generating coverage report)kill,printf(most shells except some are built-in)
Install the latest release version.
curl -fsSL https://git.io/shellspec | shor
wget -O- https://git.io/shellspec | shAdvanced installation / upgrade / uninstall
Install the specified version.
curl -fsSL https://git.io/shellspec | sh -s 0.19.1Upgrade to the latest release version.
curl -fsSL https://git.io/shellspec | sh -s -- --switchSwitch to the specified version.
curl -fsSL https://git.io/shellspec | sh -s 0.18.0 --switchHow to uninstall.
- Delete the ShellSpec executable file [default:
$HOME/bin/shellspec]. - Delete the ShellSpec installation directory [default:
$HOME/lib/shellspec].
Other usage.
$ curl -fsSL https://git.io/shellspec | sh -s -- --help
Usage: [sudo] ./install.sh [VERSION] [OPTIONS...]
or : wget -O- https://git.io/shellspec | [sudo] sh
or : wget -O- https://git.io/shellspec | [sudo] sh -s -- [OPTIONS...]
or : wget -O- https://git.io/shellspec | [sudo] sh -s VERSION [OPTIONS...]
or : curl -fsSL https://git.io/shellspec | [sudo] sh
or : curl -fsSL https://git.io/shellspec | [sudo] sh -s -- [OPTIONS...]
or : curl -fsSL https://git.io/shellspec | [sudo] sh -s VERSION [OPTIONS...]
VERSION:
Specify install version and method
e.g
1.0.0 Install 1.0.0 from git
master Install master from git
1.0.0.tar.gz Install 1.0.0 from tar.gz archive
. Install from local directory
OPTIONS:
-p, --prefix PREFIX Specify prefix [default: $HOME/.local]
-b, --bin BIN Specify bin directory [default: <PREFIX>/bin]
-d, --dir DIR Specify installation directory [default: <PREFIX>/lib/shellspec]
-s, --switch Switch version (requires installed via git)
-l, --list List available versions (tags)
--pre Include pre-release
--fetch FETCH Force command to use when install from archive (curl or wget)
-y, --yes Automatic yes to prompts
-h, --help You're looking at itArch Linux
Installation on Arch Linux from the AUR ShellSpec package using aura:
# Install the latest stable version
$ aura -A shellspecHomebrew / Linuxbrew
# Install the latest stable version
$ brew tap shellspec/shellspec
$ brew install shellspecbasher
Installation with basher
The officially supported version is ShellSpec 0.19.1 and later.
# Install from master branch
$ basher install shellspec/shellspec
# To specify a version (example: 0.19.1)
$ basher install shellspec/shellspec@0.19.1bpkg
Installation with bpkg
The officially supported version is ShellSpec 0.19.1 and later.
# Install from master branch
$ bpkg install shellspec/shellspec
# To specify a version (example: 0.19.1)
$ bpkg install shellspec/shellspec@0.19.1Archive
See Releases page if you want to download distribution archive.
Make
How to install.
Install to /usr/local/bin and /usr/local/lib
sudo make installInstall to $HOME/bin and $HOME/lib
make install PREFIX=$HOMEHow to uninstall.
sudo make uninstallmake uninstall PREFIX=$HOMEManual installation
Just get ShellSpec and create a symlink in your executable PATH!
From git
$ cd /SOME/WHERE/TO/INSTALL
$ git clone https://github.com/shellspec/shellspec.git
$ ln -s /SOME/WHERE/TO/INSTALL/shellspec/shellspec /EXECUTABLE/PATH/
# (e.g. /EXECUTABLE/PATH/ = /usr/local/bin/, $HOME/bin/)From tar.gz
$ cd /SOME/WHERE/TO/INSTALL
$ wget https://github.com/shellspec/shellspec/archive/{VERSION}.tar.gz
$ tar xzvf shellspec-{VERSION}.tar.gz
$ ln -s /SOME/WHERE/TO/INSTALL/shellspec-{VERSION}/shellspec /EXECUTABLE/PATH/
# (e.g. /EXECUTABLE/PATH/ = /usr/local/bin/, $HOME/bin/)If you can't create symlink (like default of Git for Windows), create the shellspec file.
$ cat<<'HERE'>/EXECUTABLE/PATH/shellspec
#!/bin/sh
exec /SOME/WHERE/TO/INSTALL/shellspec/shellspec "$@"
HERE
$ chmod +x /EXECUTABLE/PATH/shellspecYou can run ShellSpec without installation by using Docker. ShellSpec and specfiles run in a Docker container.
See How to use ShellSpec with Docker.
Just create your project directory and run shellspec --init to setup to your project
# Create your project directory, for example "hello".
$ mkdir hello
$ cd hello
# Initialize
$ shellspec --init
create .shellspec
create spec/spec_helper.sh
create spec/hello_spec.sh # sample
# Write your first specfile (of course you can use your favorite editor)
$ cat<<'HERE'>spec/hello_spec.sh
Describe 'hello.sh'
Include lib/hello.sh
It 'says hello'
When call hello ShellSpec
The output should equal 'Hello ShellSpec!'
End
End
HERE
# Create lib/hello.sh
$ mkdir lib
$ touch lib/hello.sh
# It will fail because the hello function is not implemented.
$ shellspec
# Write hello function
$ cat<<'HERE'>lib/hello.sh
hello() {
echo "Hello ${1}!"
}
HERE
# It will success!
$ shellspec$ shellspec --help
Usage: shellspec [options...] [files or directories...]
Using + instead of - for short options causes reverses the meaning
-s, --shell SHELL Specify a path of shell [default: "auto" (the shell running shellspec)]
ShellSpec ignores shebang and runs in the specified shell.
--path PATH Set PATH environment variable at startup
e.g. --path /bin:/usr/bin, --path "$(getconf PATH)"
--[no-]sandbox Force the use of the mock instead of the actual command
Make PATH empty (except "spec/support/bin" and mock dir) and readonly
This is not a security feature and does not provide complete isolation
--sandbox-path SANDBOX-PATH Make PATH the sandbox path instead of empty (default: empty)
--require MODULE Require a MODULE (shell script file)
-e, --env NAME=VALUE Set environment variable
--env-from ENV-SCRIPT Set environment variable from shell script file
-w, --[no-]warning-as-failure Treat warning as failure [default: enabled]
--[no-]fail-fast[=COUNT] Abort the run after first (or COUNT) of failures [default: disabled]
--[no-]fail-no-examples Fail if no examples found [default: disabled]
--[no-]fail-low-coverage Fail on low coverage [default: disabled]
The coverage threshold is specified by the coverage option
-p, --[no-]profile Enable profiling and list the slowest examples [default: disabled]
Profiler tries to use 100% ability of 1 CPU (1 core).
Therefore, not recommended for single(-core) CPU.
--profile-limit N List the top N slowest examples [default: 10]
--[no-]boost Increase the CPU frequency to boost up testing speed [default: disabled]
Equivalent of --profile --profile-limit 0
(Don't worry, this is not overclocking. This is joke option but works.)
--log-file LOGFILE Log file for %logger directive and trace [default: /dev/tty]
--keep-tempdir Do not cleanup temporary directory [default: disabled]
**** Execution ****
-q, --[no-]quick Run not-passed examples if it exists, otherwise run all [default: disabled]
not-passed examples: failure and temporary pending examples
Quick mode is automatically enabled. To disable quick mode,
delete .shellspec-quick.log on the project root directory.
-r, --repair, --only-failures Run failure examples only (Depends on quick mode)
-n, --next, --next-failure Run failure examples and abort on first failure (Depends on quick mode)
Equivalent of --repair --fail-fast --random none
-j, --jobs JOBS Number of parallel jobs to run [default: 0 (disabled)]
--random TYPE[:SEED] Run examples by the specified random type
[none] run in the defined order [default]
[specfiles] randomize the order of specfiles
[examples] randomize the order of examples (slow)
-x, --xtrace Run examples with trace output of evaluation enabled [default: disabled]
Fall back to --xtrace-only if BASH_XTRACEFD not supported.
-X, --xtrace-only Run examples with trace output only enabled [default: disabled]
The evaluation is executed, but the expectations are skipped.
--dry-run Print the formatter output without running any examples [default: disabled]
**** Output ****
--[no-]banner Show banner if exist 'spec/banner' [default: enabled]
-f, --format FORMATTER Choose a formatter for display
[p]rogress dots [default]
[d]ocumentation group and example names
[t]ap TAP format
[j]unit JUnit XML (time attributre with --profile)
[f]ailures file:line:message (suitable for editors integration)
[null] do not display anything
[debug] for developers
custom formatter name
-o, --output GENERATOR Choose a generator(s) to generate a report file(s) [default: none]
You can use the same name as FORMATTER. (multiple options allowed)
--[no-]color Enable or disable color [default: enabled if the output is a TTY]
Disable if NO_COLOR environment variable set
--skip-message VERBOSITY Mute skip message
[verbose] do not mute any messages [default]
[moderate] mute repeated messages
[quiet] mute repeated or non-temporary messages
--pending-message VERBOSITY Mute pending message
[verbose] do not mute any messages [default]
[quiet] mute non-temporary messages
--quiet Equivalent of --skip-message quiet --pending-message quiet
--(show|hide)-deprecations Show or hide deprecations details [default: show]
**** Ranges / Filters / Focus ****
You can run selected examples by specified the line numbers or ids
shellspec path/to/a_spec.sh:10 # Run the groups or examples that includes lines 10
shellspec path/to/a_spec.sh:@1-5 # Run the 5th groups/examples defined in the 1st group
shellspec a_spec.sh:10:@1:20:@2 # You can mixing multiple line numbers and ids with join by ':'
-F, --focus Run focused groups / examples only
-P, --pattern PATTERN Load files matching pattern [default: "*_spec.sh"]
-E, --example PATTERN Run examples whose names include PATTERN
-T, --tag TAG[:VALUE] Run examples with the specified TAG
-D, --default-path PATH Set the default path where looks for examples [defualt: "spec"]
**** Coverage ****
--[no-]kcov Enable coverage using kcov [default: disabled]
Requires kcov (v35 or later) and bash/zsh/ksh, parallel execution is ignored.
--kcov-path PATH Specify kcov path [default: kcov]
--kcov-options OPTIONS Additional Kcov options (coverage limits, coveralls id, etc)
Default specified options: (can be overwritten)
--include-path=.
--include-pattern=.sh
--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/
--path-strip-level=1
To include files without extension, specify --include-pattern
without '.sh' and filter with --include-*/--exclude-* options.
**** Utility ****
--init [TEMPLATE...] Initialize your project with ShellSpec
Template: Create additional files.
[git] .gitignore
[hg] .hgignore
[svn] .svnignore
--gen-bin [@COMMAND...] Generate test support commands in spec/support/bin
This is useful for run actual commands from mock/stub.
--count Count the number of specfiles and examples
--list LIST List the specfiles/examples
[specfiles] list the specfiles
[examples] list the examples with id
[examples:id] alias for examples
[examples:lineno] list the examples with lineno
[debug] for developer
The order is randomized with --random but random TYPE is ignored.
--syntax, --syntax-check Syntax check of the specfiles without running any examples
--translate Output translated specfile
--task [TASK] Run the TASK or Show the task list if TASK is not specified
-v, --version Display the version
-h, --help -h: short help, --help: long helpRun shellspec --init initializes the current directory for ShellSpec.
It creates .shellspec and spec/spec_helper.sh
Specify the shell to run with --shell option.
ShellSpec ignores shebang and runs the shell script in the specified shell.
The default is the shell running the shellspec command (usually /bin/sh).
Quick execution is a feature for rapid development and failure fixing.
When you run shellspec with --quick option first, Quick mode is automatically enabled.
When Quick mode enabled, The file .shellspec-quick.log generated on the project root directory.
If you want to disable Quick mode, delete .shellspec-quick.log.
When Quick mode enabled, the results of running examples are logged to .shellspec-quick.log
on the project root directory (even if --quick option is not specified).
Use --quick option is for rapid development. When --quick option specified, It runs examples
that not-passed (failure and temporary pending) the last time they ran.
If there are no examples that did not pass, It runs all examples.
It is designed to be added to $HOME/.shellspec instead of being specified each runs.
Use --repair and --next option is for rapid failure fixing.
It runs failed examples only (not includes temporary pending).
You can use parallel execution for fast test with --jobs option. Parallel
jobs are executed per specfile. So it is necessary to separate the specfile
for effective parallel execution.
You can randomize the execution order to detect troubles due to the test
execution order. If SEED is specified, the execution order is deterministic.
You can stop on the first (N times) failures with --fail-fast option.
NOTE: The reporter that count the number of failures and specfile execution are processed in parallel. Therefore, the specfile execution may precede the location where it stopped due to a failure.
You can trace evaluation with --xtrace or --xtrace-only option.
If BASH_XTRACEFD is implemented in the shell, you can run tests and traces at the same time.
Otherwise, run tracing only. The output format can be set with the variable PS4.
NOTE: BASH_XTRACEFD only available bash version >= 4.1 or busybox (ash) version >= 1.28.0.
Force the use of the mock instead of the actual command.
This option makes the PATH environment variable empty (except spec/support/bin) and readonly.
Support commands help to call the actual command in sandbox mode.
NOTE: This is not a security feature and does not provide complete isolation. For example, if specified with an absolute path, the actual command will be executed. If you need strict isolation, use Docker or similar technology.
You can run specific example(s) or example group(s) only.
It can be specified by line number (a_spec.sh:10:20), example id (a_spec.sh:@1-5:@1-6),
example name (--example option), tag (--tag option) and focus (--focus option).
To focus, prepend f to groups / examples in specfiles (e.g. Describe -> fDescribe, It -> fIt)
and run with --focus option.
You can specify one reporter (output to stdout) and multiple generators
(output to a file). Currently builtin formatters are progress,
documentation, tap, junit, failures, null, debug.
NOTE: Custom formatter is supported (but not documented yet, sorry).
ShellSpec has integrated coverage feature. To use this feature Kcov (v35 or later) is required.
- How to install kcov.
- Shells that support coverage are bash, zsh, and ksh.
- Coverage measures only
Theevaluation andInclude.
By default only files whose names contain .sh are coverage targeted.
If you want to include other files, you need to adjust options with --kcov-options.
# Default kcov (coverage) options
--kcov-options "--include-path=. --path-strip-level=1"
--kcov-options "--include-pattern=.sh"
--kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/"
# Example: Include script "myprog" with no extension
--kcov-options "--include-pattern=.sh,myprog"
# Example: Only specified files/directories
--kcov-options "--include-pattern=myprog,/lib/"Coverage report and cobertura.xml and sonarqube.xml files are generated under the coverage directory by Kcov.
You can easily integrate with Coveralls, Code Climate,
Codecov and more.
When the --profile option is specified, the profiler is enabled and lists the slow examples.
You can run the task with --task option.
Project directory
├─ .shellspec [Required]
├─ .shellspec-local [Optional, Ignore from VCS]
├─ .shellspec-quick.log [Optional, Ignore from VCS]
├─ report/ [Optional, Ignore from VCS]
├─ coverage/ [Optional, Ignore from VCS]
│
├─ bin/
│ ├─ your_script1.sh
│ :
├─ lib/
│ ├─ your_library1.sh
│ :
├─ libexec/
│ ├─ project-your_script1.sh
│ :
├─ spec/
│ ├─ banner [Optional]
│ ├─ spec_helper.sh [Required]
│ ├─ support/ [Optional]
│ │
│ ├─ bin/
│ │ ├─ your_script1_spec.sh
│ │ :
│ ├─ lib/
│ │ ├─ your_library1_spec.sh
│ │ :
│ ├─ libexec/
│ │ ├─ project-your_script1_spec.sh
│ :
To change the default options for the shellspec command, create options file(s).
Files are read in the order shown below, options defined last take precedence.
$XDG_CONFIG_HOME/shellspec/options$HOME/.shellspec./.shellspec./.shellspec-local(Do not store in VCS such as git)
Specify your default options with $XDG_CONFIG_HOME/shellspec/options or $HOME/.shellspec.
Specify default project options with .shellspec and overwrite to your favorites with .shellspec-local.
Log file used for Quick execution.
Directory for report output using the --output option.
Directory where kcov outputs coverage reports.
Directory where you create specfiles.
If spec/banner file exists, the banner is shown when the shellspec command
is executed. Disable that behavior with the --no-banner option.
The spec_helper.sh is loaded to specfile by the --require spec_helper option.
This file is used to define global functions, initial setting for examples, custom matchers, etc.
This directory is used to store files for custom matchers, tasks, etc.
This directory is used to store support commands.
The best place to learn how to write a specfile is the sample/spec directory. You should take a look at it ! (Those samples include failure examples on purpose.)
Describe 'sample' # example group
Describe 'bc command'
add() { echo "$1 + $2" | bc; }
It 'performs addition' # example
When call add 2 3 # evaluation
The output should eq 5 # expectation
End
End
Describe 'implemented by shell function'
Include ./mylib.sh # add() function defined
It 'performs addition'
When call add 2 3
The output should eq 5
End
End
EndYou can write a structured example by using the Describe, Context, ExampleGroup block.
Describe and Context are alias for ExampleGroup.
Example groups can be nested. They can contain example groups or examples.
NOTE: Each example group runs in a subshell.
You can describe how code behaves by using the It, Specify, Example block.
It and Specify are alias for Example.
It is composed by a maximum of one evaluation and multiple expectations.
NOTE: Each example runs in a subshell.
Todo is same as empty example.
You can easily skip the example by prefixing it with "x".
You can focus the example by prefixing it with "f".
You can only run the focused example with the --focus option.
Defines the action for verification. The evaluation begins with When.
Only one evaluation can be defined for each example.
When call echo hello world
| | |
| | +-- The rest is action for verification
| +-- The evaluation type `call` calls a function or external command.
+-- The evaluation keyword
There are two types of evaluation, When call and When run. and
When run has sub types of command, script and source.
See more details of Evaluation
When call echo hello world
Dump # stdout, stderr, statusDefines the verification. The expectation begins with The.
Verifies the subject with the matcher.
The output should equal 4
| | |
| | +-- The `equal` matcher verifies the subject value is 4.
| +-- The `output` subject uses the stdout as a subject for verification.
+-- The expectation keyword
You can reverse the verification with should not.
The output should not equal 4You can use the modifier to modify the subject.
The line 2 of output should equal 4
|
+-- The `line` modifier use the specified line 2 of output as subject.
NOTE: `of output` can be omitted.
The modifier is chainable.
The word 1 of line 2 of output should equal 4You can use ordinal numbers.
The second line of output should equal 4ShellSpec supports language chains like chai.js.
It only improves readability, does not affect the expectation: a, an, as, the.
The following two sentences have the same meaning:
The first word of second line of output should valid numberThe first word of the second line of output should valid as a numberUse Assert for using custom assertion.
It is designed for verification of side effects, not result of evaluation.
still_alive() {
ping -c1 "$1" >/dev/null
}
Describe "example.com"
It "responses"
Assert still_alive "example.com"
End
EndNOTE: To verify the evaluation result with a custom function, use the result modifier or the satisfy matcher.
There are many subjects, modifiers, matchers. please refer to the References
You can create custom subject, custom modifier and custom matcher. See sample/spec/support/custom_matcher.sh for custom matcher.
NOTE: If you want to verify using shell function, You can use result modifier or satisfy matcher. You don't necessarily have to create a custom matcher, etc.
You can skip an example by using the Skip keyword. If you want to skip only in
some cases, use a conditional skip Skip if. You can also use Pending to
indicate that the example needs to be implemented.
The (non-temporary) skip and pending is for long term skip and pending. It need time to resolve and it may commit to a version control system.
The temporary skip and pending is for short term skip and pending. Used during the current work, do not commit to a version control system.
The skip and pending without message is temporary skip and pending.
Skip "some reason" # Skip with message is non-temporary skip
Skip if "reason" condition # Skip with condition is also non-temporary skip
Skip # temporary skip (this is comment but will be displayed in the report)You can also temporary skip with blocks by prefixing with x
(xDescribe, xContext, xExample, xSpecify, xIt).
Todo (and empty example) is also treated as temporary pending.
Include the shell script file to test.
Set shell option before executing each example.
The shell option name is the long name of set or the name of shopt:
Set 'errexit:off' 'noglob:on'You can define path aliases for readability. File and Dir are alias for Path.
You can use the Data Helper which inputs data from stdin for evaluation.
The input data is specified after #| in the Data or Data:expand block.
Describe 'Data helper'
Example 'provide with Data helper block style'
Data # Use Data:expand instead if you want expand variables.
#|item1 123
#|item2 456
#|item3 789
End
When call awk '{total+=$2} END{print total}'
The output should eq 1368
End
EndYou can also use a file, function, or string as data sources.
See more details of Data
You can Data Driven Test (aka Parameterized Test) with Parameters[:style].
Describe 'example'
Parameters
"#1" 1 2 3
"#2" 1 2 3
End
Example "example $1"
When call echo "$(($2 + $3))"
The output should eq "$4"
End
EndThe following four styles are supported.
- block style (default: same as Parameters)
- value style
- matrix style
- dynamic style
See more details of Parameters
NOTE: You can also be used with the Data:expand helper.
You can define before / after hooks by using Before, After.
The hooks are called for each example.
NOTE: After hook is a place to clean up, not an assertion. If you want to assert in the After hook,
What you are looking for is probably result modifier.
You can define before all / after all hooks by using BeforeAll, AfterAll.
The hooks are called before or after all examples.
You can define before / after call hooks by using BeforeCall, AfterCall.
The hooks are called before or after a "call evaluation".
You can define before / after run hooks by using BeforeRun, AfterRun.
The hooks are called before or after a "run evaluation".
These hooks are executed in the same subshell as the "run evaluation". So you can mock/stub the function before run. And you can access a variable for evaluation after run.
%const (% is short hand) directive defines a constant value. The characters
which can be used for variable names are uppercase letters [A-Z], digits
[0-9] and underscore _ only. It can not be defined inside an example
group nor an example.
The value is evaluated during the specfile translation process. So you can access ShellSpec variables, but you can not access variable or function in the specfile.
This feature assumed use with conditional skip. The conditional skip may runs outside of the examples. As a result, sometime you may need variables defined outside of the examples.
You can use the %text directive instead of an hard-to-use heredoc with
indented code. The input data is specified after #|.
Describe '%text directive'
It 'outputs texts'
output() {
echo "start" # you can write code here
%text
#|aaa
#|bbb
#|ccc
echo "end" # you can write code here
}
result() { %text
#|start
#|aaa
#|bbb
#|ccc
#|end
}
When call output
The output should eq "$(result)"
The line 3 of output should eq 'bbb'
End
End%puts (put string) and %putsn (put string with newline) can be used instead
of (not portable) echo. Unlike echo, it does not interpret escape sequences
regardless of the shell. %- is an alias of %puts, %= is an alias of
%putsn.
Use %preserve directive to preserve the variables in subshells and external shell script.
In the following cases, %preserve is required because variables are not preserved.
When runevaluation - It runs in a subshell.- Command-based mock (
Mock) - It is an external shell script. - Function-based Mock called by command substitution
Describe '%preserve directive'
It 'preserves variables'
func() { foo=1; bar=2; baz=3; }
preserve() { %preserve bar baz:BAZ; }
AfterRun preserve
When run func
The variable foo should eq 1 # This will be failure
The variable bar should eq 2 # This will be success
The variable BAZ should eq 3 # Preserved to different variable (baz:BAZ)
End
EndOutput log messages to the log file (default: /dev/tty) for debugging.
See Parameters.
There are two ways to create a mock/stub, function-based mock and command-based mock. The function-based mock is usually recommended for performance reasons. Both can be overwritten with an internal block and will be restored when the block ends.
The function-based mock is simply (re)defined with shell function.
The command-based mock is create a temporary mock shell script and run as external command.
To accomplish this, a directory for mock commands is included at the beginning of PATH.
This is slow, but there are some advantages over function-based.
- You can use invalid characters as the shell function name.
- e.g
docker-compose(It can be defined with bash etc., but invalid as POSIX.)
- e.g
- You can use the mock command from an external shell script.
A command-based mock creates an external shell script with the contents of
a Mock block. Therefore, there are some restrictions.
- You cannot call shell functions outside the
Mockblock.- Only bash can export and call shell functions with
export -f.
- Only bash can export and call shell functions with
- To reference a variable outside the
Mockblock, that variable must be exported. %preservedirective is required to return a variable from aMockblock.
Describe 'mock example'
get_next_day() { echo $(($(date +%s) + 86400)); }
Context 'when not using mock'
It 'runs the actual date command'
When call get_next_day
The stdout should not eq 1546354800
End
End
Context 'when using function-based mock'
date() {
echo 1546268400
called=1
%preserve called
}
It 'calls the date function'
When call get_next_day
The stdout should eq 1546354800
The variable called should eq 1
End
End
Context 'when using command-based mock'
Mock date
echo 1546268400
called=1
%preserve called
End
It 'runs the mocked date command'
When call get_next_day
The stdout should eq 1546354800
The variable called should eq 1
End
End
EndSupport commands are helper commands that can be used in the specfile.
For example, it can be used in a mock function to execute the actual command.
It is recommended that the support command name be the actual command name prefixed with @.
Describe "Support commands sample"
touch() {
@touch "$@" # @touch executes actual touch command
echo "$1 was touched"
}
It "touch a file"
When run touch "file"
The output should eq "file was touched"
The file "file" should be exist
End
EndSupport commands are generate to the spec/support/bin directory by --gen-bin option.
For example, run shellspec --gen-bin @touch to generate the @touch command.
This is main purpose but support commands is just shell script, so you can also be used for other purposes. You can freely edit the support command script.
The sandbox mode is force the use of the mock. However, you may not want to require mocks in some commands.
For example, printf is a built-in command in many shells and does not require a mock,
but some shells require a mock in sandbox mode because it is an external command.
To allow printf to be called without mocking in such cases,
create a support command named printf (shellspec --gen-bin printf).
Some commands have different options between BSD and GNU. If you handle the difference in the specfile, the test will be hard to read. You can solve it with the support command.
#!/bin/sh -e
# Command name: @sed
. "$SHELLSPEC_SUPPORT_BIN"
case $OSTYPE in
*darwin*) invoke gsed "$@" ;;
*) invoke sed "$@" ;;
esacShell scripts are often made up of a single file. ShellSpec provides two ways of testing a single shell script.
This is a method for testing functions defined in shell scripts. Loading a
script with Include defines a __SOURCED__ variable available in the sourced
script. If the __SOURCED__ variable is defined, return in your shell script
process.
#!/bin/sh
# hello.sh
hello() { echo "Hello $1"; }
${__SOURCED__:+return}
hello "$1"Describe "sample"
Include "./hello.sh"
Example "hello test"
When call hello world
The output should eq "Hello world"
End
EndThis is a method to mock/stub functions and commands when executing shell scripts. By placing intercept points in your script, you can call the hooks defined in specfile.
#!/bin/sh
# today.sh
test || __() { :; }
__ begin __
date +"%A, %B %d, %Y"Describe "sample"
Intercept begin
__begin__() {
date() {
export LANG=C
command date "$@" --date="2019-07-19"
}
}
Example "today test"
When run source ./today.sh
The output should eq "Friday, July 19, 2019"
End
EndNormally, you use shellspec to run the specfile. But you can run it directly
by adding eval "$(shellspec -)" to the top of the specfile.
#!/bin/sh
# 'test.sh' with executable permission
eval "$(shellspec -)"
Describe "bc command"
bc() { echo "$@" | command bc; }
It "performs addition"
When call bc "2+3"
The output should eq 5
End
End# You can run 'test.sh' directly
$ ./test.sh
Running: /bin/sh [sh]
.
Finished in 0.12 seconds (user 0.00 seconds, sys 0.10 seconds)
1 example, 0 failures
# Also you can run via shellspec
$ shellspec test.shIf you want to know ShellSpec architecture and self test, see CONTRIBUTING.md
The specfile is a valid shell script, but a translation process is performed to implement the scope,
line number etc. Each example group block and example block is translated to commands in a subshell.
Therefore changes inside those blocks do not affect the outside of the block. In other words it realizes
local variables and local functions in the specfile. This is very useful for describing a structured spec.
If you are interested in how to translate, use the --translate option.

