1 Guide
This guide walks through using rackunit/feature for Behavior-Driven Development in Racket. Each section builds on the previous one.
1.1 Quick Start
A complete BDD workflow requires three files:
1. A feature file ("calculator.feature"):
#lang feature Feature: Calculator Scenario: Addition Given a calculator When I add 2 and 3 Then the result is 5
2. Step definitions ("calculator-steps.rkt"):
(require rackunit rackunit/feature) (provide calculator-steps) (define-steps calculator-steps (given "a calculator" (lambda (ctx) (hash-set ctx 'calc 'ready))) (when "I add {a} and {b}" (lambda (ctx a b) (hash-set ctx 'result (+ (string->number a) (string->number b))))) (then "the result is {n}" (lambda (ctx n) (check-equal? (hash-ref ctx 'result) (string->number n)) ctx)))
3. A test runner ("test-calculator.rkt"):
(require rackunit/feature) (require "calculator-steps.rkt") (require "calculator.feature") (run-features features #:steps calculator-steps)
Run with raco test test-calculator.rkt. The features identifier is automatically provided by the #lang feature module.
1.2 Feature File Syntax
Feature files use #lang feature and follow a subset of the Gherkin specification:
#lang feature @smoke Feature: Shopping Cart Background: Given an empty cart @fast Scenario: Add item When I add "Milk" to the cart Then the cart has 1 item Scenario Outline: Pricing When I add <item> at <price> Then the total is <price> Examples: | item | price | | Bread | 2 | | Eggs | 3 |
1.2.1 Keywords
Feature: —
groups related scenarios under a name. Background: —
steps that run before every scenario in the feature. Scenario: —
an individual test case with its own isolated context. Scenario Outline: —
a parameterized scenario template, expanded once per row in the Examples: table. Given, When, Then —
step keywords that express preconditions, actions, and assertions. And, But —
continuation keywords that inherit the type of the preceding step.
1.3 Step Definitions
Use define-steps to create a named list of step definitions. Each clause matches a step keyword (given, when, or then) and a pattern string:
(define-steps my-steps (given "a user named {name}" (lambda (ctx name) (hash-set ctx 'user name))) (then "the greeting is {msg}" (lambda (ctx msg) (check-equal? (hash-ref ctx 'greeting) msg) ctx)))
1.3.1 Placeholders
Text inside {braces} in a pattern becomes a capture group. Captured
values are passed to the handler as strings, in order, after the context
argument. All captures are strings —
1.3.2 Handler Contract
Every step handler receives the context hash table as its first argument, followed by any captured placeholder values. If the step has a data table or doc string, that value is passed as the final argument.
The handler must return a hash —
1.4 Running Features
The run-features function takes a list of gherkin-feature structures (as provided by #lang feature files) and runs them as rackunit test suites:
(require "calculator.feature") (require (prefix-in s: "shopping.feature")) (run-features (append features s:features) #:steps (append calc-steps shop-steps))
To require multiple feature files, use prefix-in to avoid name collisions on the features binding:
(require "calculator.feature") (require (prefix-in s: "shopping.feature"))
1.5 Lifecycle Hooks
Hooks let you run setup and teardown logic at various points. Each hook receives the current context hash (and the relevant AST node where applicable) and must return a hash:
(run-features features #:steps my-steps #:before-all (lambda (ctx) (hash-set ctx 'db (connect-db))) #:after-all (lambda (ctx) (disconnect (hash-ref ctx 'db)) ctx) #:before-scenario (lambda (ctx sc) (hash-set ctx 'tx (begin-tx ctx))) #:after-scenario (lambda (ctx sc) (rollback (hash-ref ctx 'tx)) ctx))
1.5.1 Hook Execution Order
For a feature with two scenarios, hooks execute in this order:
before-all (ctx) -> ctx' |
before-feature (ctx', feat) -> ctx'' |
before-scenario (ctx'', sc1) -> ctx-a |
before-step (ctx-a, step) -> ... |
step handler |
after-step (ctx, step) -> ... |
after-scenario (ctx, sc1) |
before-scenario (ctx'', sc2) -> ctx-b (fresh from before-feature) |
... |
after-scenario (ctx, sc2) |
after-feature (ctx'', feat) |
after-all (ctx') |
Note: each scenario starts from the context returned by #:before-feature, providing isolation between scenarios.
1.6 Data Tables
A step can have a pipe-delimited data table immediately following it:
Given the following users: |
| name | age | |
| Alice | 30 | |
| Bob | 25 | |
The table is passed to the step handler as the final argument —
(given "the following users:" (lambda (ctx table) (define header (car table)) (define rows (cdr table)) (hash-set ctx 'users rows)))
1.7 Doc Strings
A step can have a triple-quoted doc string following it:
Given a document: |
""" |
Hello world |
from a doc string |
""" |
The doc string is passed to the handler as a single string with leading indentation stripped:
(given "a document:" (lambda (ctx doc) (hash-set ctx 'document doc)))
1.8 Tags
Tags annotate features and scenarios with @-prefixed labels. Place them on the line before the feature or scenario keyword:
@smoke |
Feature: Login |
@fast @critical |
Scenario: Valid credentials |
Given a registered user |
... |
Tags are stored in the gherkin-feature-tags and gherkin-scenario-tags fields as lists of strings (e.g., '("@smoke")). You can use them in hooks to implement tag-based filtering or conditional logic.
1.9 Scenario Outlines
A Scenario Outline is a template expanded at parse time into one concrete scenario per row in the Examples: table. Use angle-bracket placeholders (<name>) in steps:
#lang feature Feature: Arithmetic Scenario Outline: Operations Given a calculator When I add <a> and <b> Then the result is <result> Examples: | a | b | result | | 1 | 2 | 3 | | 4 | 5 | 9 |
This produces two scenarios: Operations (a=1, b=2, result=3) and Operations (a=4, b=5, result=9). The step text has the placeholders replaced with values, so existing step definitions match without any changes.