Skip to content

lambdaisland/kaocha-cucumber

Repository files navigation

lambdaisland/kaocha-cucumber

CircleCI cljdoc badge Clojars Project codecov

Kaocha support for Cucumber tests.

Cucumber is a "Behavior Driven Development" tool, a way to write "feature" tests in a human readable format known as Gherkin syntax.

This project adds Cucumber support to Kaocha, allowing to write and Cucumber tests using Clojure.

Getting started

This assumes you already have Kaocha setup, including a tests.edn and bin/kaocha wrapper.

Start by adding lambdaisland/kaocha-cucumber to your project,

;; deps.edn
{:paths [,,,]
 :deps {,,,}
 :aliases
 {:test {:extra-deps {lambdaisland/kaocha { ,,, }
                      lambdaisland/kaocha-cucumber { ,,, }}}}}

Create a directory which will contain your Cucumber tests (*.feature files), and one which will contain step definitions (*.clj files).

mkdir -p test/features test/steps_definitions

Now add a Cucumber test suite to tests.edn

#kaocha/v1
{:tests [{:id           :unit
          :type         :kaocha.type/clojure.test
          :source-paths ["src"]
          :test-paths   ["test/unit"]}

         {:id                  :features
          :type                :kaocha.type/cucumber
          :source-paths        ["src"]
          :test-paths          ["test/features"]
          :cucumber/glue-paths ["test/steps_definitions"]
          :cucumber/locale     "de-DE"  ; optional. Currently only for number
                                        ; format parsing, passed to
                                        ; java.util.Locale/forLanguageTag
          }]}

Finally create a file test/features/coffeeshop.feature with the following contents

Feature: Coffee shop order fulfilment

  The coffee shop contains a counter where people can place orders.

  Background:
    Given the following price list
      | Matcha Latte | 4.00 |
      | Green Tea    | 3.50 |

  Scenario: Getting change
    When I order a Matcha Latte
    And pay with $5.00
    Then I get $1.00 back

And run it

bin/kaocha features

Since the steps aren't implemented yet, Kaocha will tell you what you're missing.

$ bin/kaocha features
[(P)]

PENDING in test.features.coffeeshop/line-10 (test/features/coffeeshop.feature:10)
You can implement missing steps with the snippets below:
(When "I order a Matcha Latte" [state]
  ;; Write code here that turns the phrase above into concrete actions
  (pending!))

(And "pay with ${double}" [state double1]
  ;; Write code here that turns the phrase above into concrete actions
  (pending!))

(Then "I get ${double} back" [state double1]
  ;; Write code here that turns the phrase above into concrete actions
  (pending!))

1 tests, 0 assertions, 1 pending, 0 failures.

Create a file test/step_definitions/coffeeshop_steps.clj, copy the sample code snippets over, and add a namespace declaration, pulling in lambdaisland.cucumber.dsl and clojure.test.

(ns coffeeshop-steps
  (:require [lambdaisland.cucumber.dsl :refer :all]
            [clojure.test :refer :all]))

(Given "the following price list" [state table]
  (assoc state
         :price-list
         (into {}
               (map (fn [[k v]]
                      [k (Double/parseDouble v)]))
               table)))

(When "I order a (.*)" [state product]
  (update state :order conj product))

(And "pay with ${double}" [{:keys [price-list order] :as state} paid]
  (doseq [product order]
    (is (contains? price-list product)))

  (let [total (apply + (map price-list order))]
    (is (<= total paid))

    (assoc state
           :total total
           :paid paid
           :change (- paid total))))

(Then "I get ${double} back" [{:keys [change] :as state} expected]
  (is (= expected change))
  state)

Each step is followed by a pattern, a string which is interpreted as either a regular expression or a Cucumber expression, and then followed by an argument list (binding form) and a function body.

The first argument is always the current state. This is a map that is passed from one step to the next as it is executed. The first step receives an empty map, the next step receives the return value from the first step, etc.

Any remaining arguments correspond with capturing groups or "output parameters" in the pattern.

Inside the step definitions you use clojure.test style assertions.

Here's the complete file structure of this example:

.
├── bin
│   └── kaocha
├── deps.edn
├── test
│   ├── features
│   │   └── coffeeshop.feature
│   └── step_definitions
│       └── coffeeshop_steps.clj
└── tests.edn

Running through a REPL

When running through a REPL, we need to disable the glue-cache. Otherwise, after we change the steps_definitions, we will still run the old steps.

The disable command is (reset! lambdaisland.cucumber.jvm/glue-cache nil).

Example:

Clojure 1.10.3
OpenJDK 64-Bit Server VM 17.0.6+10-LTS
Interrupt: Control+C
Exit:      Control+D or (exit) or (quit)
user=> (require '[lambdaisland.cucumber.jvm])
nil
user=> (require '[kaocha.repl :as k])
nil
;; First time to run
user=> (k/run :features)

PENDING in coffeeshop/getting-change (test/features/coffeeshop.feature:10)
You can implement missing steps with the snippets below:
(Then "I get ${double} back" [state double1]
  ;; Write code here that turns the phrase above into concrete actions
  (pending!))

1 tests, 2 assertions, 1 pending, 0 failures.

PENDING coffeeshop/getting-change (test/features/coffeeshop.feature:10)
#:kaocha.result{:count 1, :pass 2, :error 0, :fail 0, :pending 1}

;; Now, we change the steps_definitions
;; We need to disable the glue-cacahe before re-run
user=> (reset! lambdaisland.cucumber.jvm/glue-cache nil)
nil
user=> (k/run :features)
]
Randomized with --seed 279015433

FAIL in coffeeshop/getting-change (test/features/coffeeshop.feature:13)
I get $1.00 back
Expected:
  100.0
Actual:
  -100.0 +496.0
1 tests, 3 assertions, 1 failures.
#:kaocha.result{:count 1, :pass 2, :error 0, :fail 1, :pending 0}

Tags

Following standard Gherkin syntax, you may add tags by prefixing them with @. For example, you could add a @customer-facing tag to the previous test/features/coffeeshop.feature.

This would tag the whole feature with @customer-facing:

@customer-facing
Feature: Coffee shop order fulfilment

This would tag a scenario with @customer-facing:

@customer-facing
  Scenario: Getting change

Then, you can use --focus-meta or --skip-meta in order to select only tagged tests or skip them respectively:

bin/kaocha --focus-meta customer-facing

Parameter Types

Data tables will be converted to a vector of vectors, other types will be passed on as-is.

To implement Custom Parameter Types, add a :cucumber/parameter-types to your config.

#kaocha/v1
{:tests [{:id :cukes
          :type :kaocha.type/cucumber
          :source-paths ["src"]
          :test-paths ["test/features" "test/support"]
          :cucumber/glue-paths ["test/step_definitions"]
          :cucumber/parameter-types
          [#:cucumber.parameter
           {:name "color"
            :regexp "red|blue|yellow|green"
            :class kaocha.cucumber.extra_types.Color
            :transformer kaocha.cucumber.extra-types/parse-color}]}]}

Following keys are understood

:cucumber.parameter/name                     ;;=> string?
:cucumber.parameter/transformer              ;;=> qualified-symbol?
:cucumber.parameter/regexp                   ;;=> string?
:cucumber.parameter/class                    ;;=> simple-symbol?
:cucumber.parameter/suggest?                 ;;=> boolean?
:cucumber.parameter/prefer-for-regexp-match? ;;=> boolean?

Relationship with cucumber-jvm-clojure / lein-cucumber

The existing cucumber-jvm-clojure and lein-cucumber are based on a very old version of cucumber-jvm, and appear to be unmaintained. Because of this kaocha-cucumber is built directly on top of cucumber-jvm. Some of the code in lambdaisland.cucumber.jvm and lambdaisland.cucumber.dsl is adapted from these earlier projects.

If you are migrating existing code bases then note that the DSL syntax is different. Patterns are given as strings, not regular expressions, the first "state" argument is a new introduction, and certain argument types are coerced to Clojure types now.

If you have a code base of significant size based on these legacy projects then please open a ticket, we might be able to create a separate legacy DSL namespace that's compatible with old tests.

Lambda Island Open Source

 

kaocha-cucumber is part of a growing collection of quality Clojure libraries created and maintained by the fine folks at Gaiwan.

Pay it forward by becoming a backer on our Open Collective, so that we may continue to enjoy a thriving Clojure ecosystem.

You can find an overview of our projects at lambdaisland/open-source.

 

 

Contributing

Everyone has a right to submit patches to kaocha-cucumber, and thus become a contributor.

Contributors MUST

  • adhere to the LambdaIsland Clojure Style Guide
  • write patches that solve a problem. Start by stating the problem, then supply a minimal solution. *
  • agree to license their contributions as MPL 2.0.
  • not break the contract with downstream consumers. **
  • not break the tests.

Contributors SHOULD

  • update the CHANGELOG and README.
  • add tests for new functionality.

If you submit a pull request that adheres to these rules, then it will almost certainly be merged immediately. However some things may require more consideration. If you add new dependencies, or significantly increase the API surface, then we need to decide if these changes are in line with the project's goals. In this case you can start by writing a pitch, and collecting feedback on it.

* This goes for features too, a feature needs to solve a problem. State the problem it solves, then supply a minimal solution.

** As long as this project has not seen a public release (i.e. is not on Clojars) we may still consider making breaking changes, if there is consensus that the changes are justified.

License

Copyright © 2019-2021 Arne Brasseur and Contributors

Licensed under the term of the Eclipse Public License 1.0, see LICENSE.txt.