| name | gherclj |
| description | Use this skill when implementing gherclj feature steps, working on beads that reference .feature files, or writing defgiven/defwhen/defthen definitions and their helpers. Ensures step definitions follow gherclj conventions — especially that defthen helpers assert results. |
gherclj Step Implementation
When This Skill Applies
Use this skill whenever you are implementing step definitions for .feature files in a gherclj project. This includes any bead that references a feature file or involves writing defgiven, defwhen, or defthen steps and their helper functions.
Feature Contract Integrity
Approved .feature files are behavioral contracts.
- Do NOT semantically weaken, reinterpret, or rewrite approved scenarios without user approval.
- Clarifying wording changes are fine only when they preserve the approved behavior exactly.
- If implementation and approved feature text diverge, stop and raise the mismatch instead of changing the scenario to fit partial implementation.
Step Definitions Must Exercise Real Behavior
Helper functions should test real product behavior through real code paths where feasible.
- Prefer calling production entry points, public APIs, application services, or other real seams the product already exposes.
- If a scenario needs a seam, prefer a real public seam or an explicitly approved test hook.
- Do not move product logic into helper functions just because the implementation is missing.
Forbidden Acceptance-Test Shortcuts
- Do NOT inject unimplemented product behavior directly in helper functions just to make scenarios pass.
- Do NOT add acceptance-test-only shims that simulate promised behavior the product does not actually implement.
- Passing feature scenarios must reflect actual implemented behavior, not test-only shortcuts.
- A bead is not complete if the feature passes only because the helpers fake missing behavior.
Architecture: Steps and Helpers
Step definitions are pure routing — a phrase mapped to a helper function reference. All logic lives in a separate helpers namespace.
spec/<app>/features/steps/<feature>.clj — phrase → helper routing
spec/<app>/features/helpers/<feature>.clj — actual test logic
Steps file — pure routing
Use helper! to declare the helper namespace, then route each phrase to its helper function:
(ns myapp.features.steps.auth
(:require [gherclj.core :refer [defgiven defwhen defthen helper!]]
[myapp.features.helpers.auth]))
(helper! myapp.features.helpers.auth)
(defgiven "a user {name:string} with role {role:string}" auth/create-user!)
(defwhen "the user logs in" auth/user-logs-in!)
(defthen "the response status should be {status:int}" auth/response-status)
Helpers file — logic and assertions
(ns myapp.features.helpers.auth
(:require [gherclj.core :as g]
[myapp.auth :as app]))
(defn create-user! [name role]
(g/assoc! :user {:name name :role role}))
(defn user-logs-in! []
(let [{:keys [role]} (g/get :user)]
(g/assoc! :response (app/authenticate role))))
(defn response-status [expected]
(g/should= expected (g/get-in [:response :status])))
Step Types and Their Responsibilities
defgiven — Set up preconditions
Given helpers mutate state to establish preconditions. No assertions needed.
defwhen — Perform actions
When helpers perform the action under test. No assertions needed.
defthen — Assert results
Then helpers MUST assert. A defthen helper that returns a value without asserting is a bug — it produces 0 assertions and silently passes.
If your project uses a single framework, you can use its native assertions directly (e.g., speclj's should=). Use g/should= when helpers need to be framework-agnostic — for example, when the project runs tests under both speclj and clojure.test.
;; WRONG — no assertion, silently passes
(defn response-status [expected]
(g/get-in [:response :status]))
;; RIGHT — asserts the expected value
(defn response-status [expected]
(g/should= expected (g/get-in [:response :status])))
Common Assertion Patterns
;; Exact match
(g/should= expected (g/get :key))
;; Truthiness
(g/should (g/get :key))
(g/should-not (g/get :key))
;; Nil check
(g/should-be-nil (g/get :key))
(g/should-not-be-nil (g/get :key))
;; Collection
(g/should= expected-vec (g/get :items))
State Management
Helpers use gherclj.core for state, aliased as g:
g/assoc!, g/assoc-in! — set state
g/get, g/get-in — read state
g/update!, g/update-in! — modify state
g/swap! — arbitrary state transformation
g/dissoc! — remove state
g/reset! — called automatically before each scenario
Running Features and Scenarios
You can run a whole feature file by passing its path, or run a specific scenario by passing a file:line selector as a positional argument. A selector matches the scenario whose declaration contains the given line.
gherclj features/adventure/dragon_cave.feature
gherclj features/adventure/dragon_cave.feature:42
gherclj features/adventure/dragon_cave.feature \
features/adventure/moon_castle.feature:73
Feature paths and location selectors combine with normal options like -f, -e, -o, and tag filters.
Discovering and Auditing Steps
gherclj steps
gherclj match "the user logs in"
gherclj ambiguity
gherclj unused
Use gherclj steps to understand what steps already exist before writing new ones. Use gherclj match to verify a phrase routes to the intended step. Use gherclj ambiguity as a pre-flight check before running. Use gherclj unused during cleanup to find dead step definitions.
All four subcommands support --json and --edn for structured output.
Verification
After implementing steps, always run the feature specs and verify:
- Assertion count > 0 — If you see
0 assertions, your defthen helpers are not asserting
- No unexpected pending — Pending scenarios mean step text isn't matching registered steps
- Real behavior is exercised — Scenarios pass because the product implements the behavior, not because helpers simulated it
- Run:
bb features (or bb test for everything)
# Good — assertions present
40 examples, 0 failures, 68 assertions, 5 pending
# Bad — no assertions means defthen helpers aren't asserting
40 examples, 0 failures, 0 assertions, 5 pending
Definition of Done
A feature implementation bead is NOT complete until:
- Step definition file exists —
spec/<app>/features/steps/<feature>.clj
- Helper file exists —
spec/<app>/features/helpers/<feature>.clj
- All scenarios run — Remove the
@wip tag from the feature file
- No pending scenarios — Every scenario's step text matches a registered step
- Assertions > 0 — Every
defthen helper asserts
- No fake behavior in helpers — Helpers are not simulating missing product behavior
- All pass —
bb features shows 0 failures
Do NOT close a bead if the feature file still has @wip, if scenarios are pending, or if the feature only passes because the helpers fake missing behavior.