Basic given-when-then steps for ExUnit tests.
I have previously used White Bread and Cabbage for writing Cucumber-style (Gherkin) tests, but decided I didn't really need the extra complexity over simple ExUnit tests. However, I quite like writing acceptance tests in a "given, when, then" format – I find it makes the described functionality easier to read, even if you aren't going the full BDD route and writing the scenarios with your customers.
This library is an experiment to try to come up with a minimal layer on top of ExUnit to allow this. It was also an excuse to delve into the world of Elixir metaprogramming.
Kale does not implement the full gherkin language. Instead it uses simple
string matching (no regexes) with the ability to explicitly interpolate values
by surrounding them with {}
.
As a nod to Spinach.
Kale is available in Hex, and can be installed
by adding it to your list of dependencies in mix.exs
:
def deps do
[
{:kale, "~> 0.8.0", only: :test}
]
end
Then run mix deps.get
.
If you want to use Kale's formatter rules (to avoid having parens inserted
around the arguments of scenario
, defwhen
etc), import it in your
.formatter.exs
:
[
# ...
import_deps: [:kale]
]
In your test, use Kale
:
defmodule MyTest do
use ExUnit.Case, async: true
use Kale
# ...
end
Then write a feature
(which corresponds to an ExUnit describe
) containing
one or more scenarios:
feature "Feature description here" do
scenario "Scenario description here", """
Given some precondition
When action {foo} happens
Then the result is {bar}
"""
scenario "Another scenario", """
...
"""
end
Each scenario has a description, and a multiline string containing a list of
steps, which should all begin with one of Given
, When
, Then
, And
or
But
. Lines beginning with anything else are treated as comments and ignored.
Each unique step needs a definition, which needs to be created in the test
module by calling defgiven
, defwhen
or defthen
. Technically it doesn't
matter which of the three you use, but it's recommended to use the one that
matches the step for redability.
The simplest form of step definition just takes a literal string argument matching the step from the scenario, and a block of code to execute:
defgiven "some precondition" do
# ...
end
If you need to access the ExUnit context, it can be provided as a second argument:
defgiven "some precondition", context do
login(context.user)
end
You can also pattern match specific values from the context:
defgiven "some precondition", %{user: user} do
login(user)
end
If a step definition returns a {:reply, context}
tuple, where context
is a
map or keyword list, it will be merged into the context for future steps in the
same scenario. For example, to store a value for session
:
defgiven "some precondition", %{user: user} do
session = login(context.user)
{:reply, session: session}
end
Values can be interpolated by wrapping them in {}
. The value from the step in
the scanario will be available within the body of the step definition using the
variable name provided:
defwhen "action {action} happens" do
do_something(action)
end
Tests are run just like normal ExUnit tests (which is what they compile to). This includes being able to specify the line number of the scenario or feature.
Tests are reported as "features" by ExUnit, separately from other tests:
$ mix test
................
Finished in 0.08 seconds
6 features, 10 tests, 0 failures
Randomized with seed 955579
To build the project after cloning:
$ mix deps.get
$ make
## TODO
* More helpful errors when no step matches