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.