gocuke-api.md

  1# gocuke API reference
  2
  3## Contents
  4
  5- [Runner](#runner)
  6- [Suite struct](#suite-struct)
  7- [Step discovery](#step-discovery)
  8- [Manual step registration](#manual-step-registration)
  9- [Injected types](#injected-types)
 10- [Data tables](#data-tables)
 11- [Doc strings](#doc-strings)
 12- [Hooks](#hooks)
 13- [Property-based testing with rapid](#property-based-testing-with-rapid)
 14- [Tag filtering](#tag-filtering)
 15- [Running tests](#running-tests)
 16
 17## Runner
 18
 19Create a runner in a standard `TestXxx` function:
 20
 21```go
 22func TestFeatureName(t *testing.T) {
 23    gocuke.NewRunner(t, &mySuite{}).Run()
 24}
 25```
 26
 27The runner is fluent — chain options before `.Run()`:
 28
 29```go
 30gocuke.NewRunner(t, &mySuite{}).
 31    Path("features/vcs/**/*.feature"). // custom glob (default: features/**/*.feature)
 32    Tags("@smoke"). // tag filter expression
 33    ShortTags("not @slow"). // applied with go test -short
 34    NonParallel(). // disable t.Parallel()
 35    Step(`^custom regex$`, (*mySuite).CustomMethod). // manual step
 36    Before(func() { /* setup */ }). // runner-level hook
 37    After(func() { /* teardown */ }).
 38    Run()
 39```
 40
 41## Suite struct
 42
 43Holds state for one scenario. A new instance is created per scenario — no cross-contamination. Embed `gocuke.TestingT` to get assertion methods.
 44
 45```go
 46type mySuite struct {
 47    gocuke.TestingT // auto-injected — provides Fatal, Log, etc.
 48    // per-scenario state
 49    result string
 50    err    error
 51}
 52```
 53
 54Any assertion library accepting a `testing.T`-like interface works because the suite itself satisfies that interface.
 55
 56## Step discovery
 57
 58gocuke converts step text to a method name via reflection:
 59
 601. Split step text on spaces
 612. Skip numeric and quoted tokens (they become parameters)
 623. PascalCase remaining words, strip punctuation
 634. Concatenate
 64
 65| Step text                     | Method signature                       |
 66| ----------------------------- | -------------------------------------- |
 67| `I have 5 cukes`              | `IHaveCukes(a int64)`                  |
 68| `I spend <amount>`            | `ISpend(a int64)`                      |
 69| `I load a "red" one`          | `ILoadAOne(a string)`                  |
 70| `the result is 1.5`           | `TheResultIs(a *apd.Decimal)`          |
 71| `I see the following` + table | `ISeeTheFollowing(t gocuke.DataTable)` |
 72
 73When a step has no matching method, gocuke prints a copy-pasteable suggestion with the correct signature.
 74
 75## Manual step registration
 76
 77For complex patterns or when auto-discovery doesn't fit:
 78
 79```go
 80gocuke.NewRunner(t, &suite{}).
 81    Step(`^I send "(GET|POST|PUT|DELETE)" to "([^"]*)"$`, (*suite).ISendTo).
 82    Step(regexp.MustCompile(`the response code is (\d+)`), (*suite).TheResponseCodeIs).
 83    Run()
 84```
 85
 86Manual registrations take priority over auto-discovered methods.
 87
 88## Injected types
 89
 90Exported fields of these types on the suite struct are auto-populated:
 91
 92| Type              | What you get                                   |
 93| ----------------- | ---------------------------------------------- |
 94| `gocuke.TestingT` | Current `*testing.T` (or rapid wrapper)        |
 95| `gocuke.Scenario` | Scenario metadata: `Name()`, `Tags()`, `URI()` |
 96| `gocuke.Step`     | Current step metadata (nil in hooks)           |
 97| `*rapid.T`        | Rapid handle (triggers property-based mode)    |
 98
 99## Data tables
100
101```go
102func (s *suite) TheFollowingUsersExist(table gocuke.DataTable) {
103    // Raw access
104    table.NumRows() // total rows including header
105    table.NumCols()
106    table.Cell(0, 1) // 0-indexed → *Cell
107
108    // Header-based access
109    ht := table.HeaderTable() // first row = column names
110    ht.NumRows() // rows excluding header
111    ht.Get(0, "name") // row offset + column name → *Cell
112}
113```
114
115Cell methods: `.String()`, `.Int64()`, `.BigInt()`, `.Decimal()`.
116
117## Doc strings
118
119```go
120func (s *suite) TheConfigContains(doc gocuke.DocString) {
121    doc.Content // raw string content
122    doc.MediaType // "json", "yaml", etc. (empty if unannotated)
123}
124```
125
126Must be the last parameter.
127
128## Hooks
129
130Define methods on the suite struct:
131
132```go
133func (s *suite) Before(scenario gocuke.Scenario) { /* per-scenario setup */ }
134func (s *suite) After() { /* always runs, even on failure */ }
135func (s *suite) BeforeStep() { /* before each step */ }
136func (s *suite) AfterStep(step gocuke.Step) { /* after each step */ }
137```
138
139Hooks can also be registered on the runner for shared setup:
140
141```go
142gocuke.NewRunner(t, &suite{}).
143    Before(func() { /* runner-level before */ }).
144    After(func() { /* runner-level after */ }).
145    Run()
146```
147
148## Property-based testing with rapid
149
150If any step method takes `*rapid.T` as its first parameter (after the receiver), the entire scenario is wrapped in `rapid.Check()`:
151
152```go
153func (s *suite) AnyValidInput(t *rapid.T) {
154    s.input = rapid.String().Draw(t, "input")
155}
156```
157
158- The scenario runs hundreds of times with different random inputs
159- On failure, rapid shrinks to a minimal reproducing case
160- `*rapid.T` can also be an exported field on the suite for universal access
161
162Tag these scenarios `@property` and use `ShortTags("not @property")` to skip them during quick iterations.
163
164## Tag filtering
165
166```go
167// In test code
168gocuke.NewRunner(t, &suite{}).
169    Tags("@smoke"). // always applied
170    Tags("(@smoke or @unit) and not @slow"). // complex expression
171    ShortTags("not @property"). // applied with -short
172    Run()
173```
174
175```bash
176# From command line (global)
177go test ./... -gocuke.tags "not @integration"
178go test ./... -short
179```
180
181## Running tests
182
183```bash
184go test ./... # all tests, parallel by default
185go test ./... -v # verbose step-by-step output
186go test ./... -run TestVCSDetection # single test function
187go test ./... -gocuke.tags "@wip" # tag filter
188go test ./... -short # trigger ShortTags
189go test ./... -count=1 # disable test caching
190```
191
192Each Feature becomes a `t.Run(featureName)` subtest. Each Scenario (including each Outline row) becomes a nested `t.Run`. Tests run in parallel by default — use `NonParallel()` when scenarios share external state.