On this page:
1.1 Quick Start
1.2 Feature File Syntax
1.2.1 Keywords
1.3 Step Definitions
1.3.1 Placeholders
1.3.2 Handler Contract
1.4 Running Features
1.5 Lifecycle Hooks
1.5.1 Hook Execution Order
1.6 Data Tables
1.7 Doc Strings
1.8 Tags
1.9 Scenario Outlines
9.0

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 — convert with string->number or similar as needed.

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 — this becomes the context for the next step.

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 — a list of lists of strings (rows of cells). The first row is typically a header:

(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.