This document outlines the development workflow when using Clornichon for Behavior-Driven Development (BDD) in Clojure projects. It covers the complete journey from writing scenarios to executing and maintaining them.
The Clornichon development workflow follows these main steps:
.feature files)deffeatureLet's explore each step in detail.
Feature files use the Gherkin syntax and typically have a .feature extension. They describe behaviors from a user's perspective.
Feature: Shopping Cart
As a customer
I want to manage items in my cart
So that I can purchase what I need
Scenario: Add item to empty cart
Given I have an empty shopping cart
When I add "Clojure Programming" book to the cart
Then my cart should contain 1 item
And the item should be "Clojure Programming" book
Scenario: Calculate discounts
Given the following products in catalog:
| product | price | category |
| Keyboard | 100 | hardware |
| Mouse | 50 | hardware |
| Clojure | 40 | book |
When I apply the "SUMMER10" discount code
Then the prices should be:
| product | discounted_price |
| Keyboard | 90 |
| Mouse | 45 |
| Clojure | 36 |
Scenario Outline: Apply tax based on location
Given a product with price <base_price>
When shipping to <location>
Then the final price should be <final_price>
Examples:
| base_price | location | final_price |
| 100 | US | 108 |
| 100 | EU | 120 |
| 100 | AU | 110 |
Doc strings (delimited by triple quotes) are useful for passing multi-line text content:
Scenario: Create a blog post with markdown
Given I am logged in as an author
When I create a new blog post with content:
"""
# Introduction to Clojure
Clojure is a dynamic, general-purpose programming language.
## Key Features
- Functional programming
- Immutable data structures
- Runs on the JVM
"""
Then the post should be formatted as HTML
And the title should be "Introduction to Clojure"
Doc strings are commonly used for:
After writing the feature file, you need to reference it in your Clojure code using deffeature.
(ns my-project.shopping-cart-test
(:require [clojure.test :refer :all]
[scenari.v2.core :as scenari :refer [deffeature]]))
;; Reference to the feature file
(deffeature shopping-cart "resources/features/shopping_cart.feature")
This creates a test that can be executed by Clojure's test runner. The deffeature macro:
You can customize the feature execution with options:
(deffeature shopping-cart "resources/features/shopping_cart.feature"
{:pre-run [#'setup-database]
:post-run [#'teardown-database]
:pre-scenario-run [#'setup-cart]
:post-scenario-run [#'cleanup-cart]
:default-scenario-state {:user-id "test-user"}})
Step definitions (also called "glue code") connect the Gherkin steps with actual Clojure code. Clornichon provides macros for defining these connections.
(ns my-project.shopping-cart-test
(:require [clojure.test :refer :all]
[scenari.v2.core :as scenari :refer [deffeature defgiven defwhen defthen]]))
(defgiven "I have an empty shopping cart" [state]
(assoc state :cart []))
(defwhen "I add {string} book to the cart" [state book-title]
(update state :cart conj {:title book-title :type :book}))
(defthen "my cart should contain {number} item" [state item-count]
(is (= item-count (count (:cart state))))
state)
(defthen "the item should be {string} book" [state book-title]
(is (= book-title (-> state :cart first :title)))
state)
Clornichon supports various parameter types in step definitions:
A sentence matcher is a cucumber expression, so its tokens are cucumber's own:
{string}: a quoted string — single or double quotes — passed without its quotes{int}, {float}, {word}, and the other built-in types{number}: clornichon's own, kept for the glues written before the others existed; it accepts a sign and decimalsapple(s) and alternation hot/cold; a literal ( or / must be escaped (\/)A glue defined with a #"..." literal stays a plain regex whatever it contains — reading it as an expression would turn a ([^"]*) into optional text and raise on its \" — and it matches the whole sentence, as re-matches did. Its capture groups become the arguments, so a group that only groups must be made non-capturing ((?:a|b)). A string sentence wrapped in ^...$ or /.../ is read as a regex too.
(defgiven "the following products in catalog:" [state table-data]
(assoc state :products
(into {} (map (fn [row] [(:product row) row]) table-data))))
(defwhen "I create a new blog post with content:" [state doc-string]
;; doc-string contains the multi-line text from the feature file
(let [parsed-post (parse-markdown doc-string)]
(assoc state :post {:content doc-string
:title (extract-title parsed-post)
:html (markdown->html doc-string)})))
Each step function receives the state from the previous step and must return the (possibly modified) state for the next step. This allows for data to flow through your scenario.
When a feature is executed, Clornichon performs the following steps:
The step matching process is a key part of Clornichon:
The simplest way to execute Clornichon tests is through the standard Clojure test runner:
clojure -M:test # Run all tests
Clornichon integrates with Kaocha for more advanced test execution:
clojure -M:test -m kaocha.runner # Run all tests
clojure -M:test -m kaocha.runner --focus my-test # Run specific test
The test output will show each scenario and step execution:
________________________
@checkout
Feature : Shopping Cart
A cart holds items and can be emptied.
Testing scenario : Add item to empty cart
Given I have an empty shopping cart (from my-project.glue/"I have an empty shopping cart")
When I add "Clojure Programming" book to the cart (from my-project.glue/"I add {string} book to the cart")
Then my cart should contain 1 item (from my-project.glue/"my cart should contain {number} item")
And the item should be "Clojure Programming" book (from my-project.glue/"the item should be {string} book")
________________________
The output is colorized along the gherkin syntax - bold cyan keywords, yellow
{string}/{number} parameters, cyan tags, grey descriptions, docstrings and
datatables - and the sentence takes the colour of the step's outcome: green when it
passed, red when it failed, grey when it never ran. Pass --no-color (or run under
kaocha with colour disabled) for plain output.
When a step fails, Clornichon provides information about the failure:
Then my cart should contain 1 item (from my-project.glue/"my cart should contain {number} item")
Step failed
FAIL in () (glue.clj:42)
expected: (= 1 (count (:items state)))
actual: (not (= 1 0))
And the item should be "Clojure Programming" book (from my-project.glue/"the item should be {string} book")
Add item to empty cart FAILED
The failing step is printed in red, and every step after it in grey - they are reported as pending rather than dropped, so the scenario stays readable end to end.
The state passed between steps can be examined in the test output when there's a failure.
When multiple step definitions match a step, Clornichon uses namespace proximity to choose:
You can extend Clornichon with custom parameter types by creating specialized regex patterns in your step definitions.
Clornichon supports several hook points for setup and teardown:
The Clornichon development workflow provides a structured approach to Behavior-Driven Development in Clojure. By following the pattern of writing features, defining glue code, and executing tests, you can create living documentation that verifies your application's behavior.
Remember that the true value of BDD comes from the collaborative process—use feature files as a communication tool between developers, testers, and domain experts to ensure a shared understanding of requirements and behaviors.
Can you improve this documentation? These fine people already did:
Hiram MADELAINE & davidpanzaEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |