/kale

Basic given-when-then steps for ExUnit tests

Primary LanguageElixirMIT LicenseMIT

Kale

Basic given-when-then steps for ExUnit tests.

Why?

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 {}.

Why "Kale"?

As a nod to Spinach.

Installation

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]
]

Usage

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

Development

To build the project after cloning:

$ mix deps.get
$ make

## TODO

  * More helpful errors when no step matches