gocuke API reference
Contents
- Runner
- Suite struct
- Step discovery
- Manual step registration
- Injected types
- Data tables
- Doc strings
- Hooks
- Property-based testing with rapid
- Tag filtering
- Running tests
Runner
Create a runner in a standard TestXxx function:
func TestFeatureName(t *testing.T) {
gocuke.NewRunner(t, &mySuite{}).Run()
}
The runner is fluent — chain options before .Run():
gocuke.NewRunner(t, &mySuite{}).
Path("features/vcs/**/*.feature"). // custom glob (default: features/**/*.feature)
Tags("@smoke"). // tag filter expression
ShortTags("not @slow"). // applied with go test -short
NonParallel(). // disable t.Parallel()
Step(`^custom regex$`, (*mySuite).CustomMethod). // manual step
Before(func() { /* setup */ }). // runner-level hook
After(func() { /* teardown */ }).
Run()
Suite struct
Holds state for one scenario. A new instance is created per scenario — no cross-contamination. Embed gocuke.TestingT to get assertion methods.
type mySuite struct {
gocuke.TestingT // auto-injected — provides Fatal, Log, etc.
// per-scenario state
result string
err error
}
Any assertion library accepting a testing.T-like interface works because the suite itself satisfies that interface.
Step discovery
gocuke converts step text to a method name via reflection:
- Split step text on spaces
- Skip numeric and quoted tokens (they become parameters)
- PascalCase remaining words, strip punctuation
- Concatenate
| Step text | Method signature |
|---|---|
I have 5 cukes |
IHaveCukes(a int64) |
I spend <amount> |
ISpend(a int64) |
I load a "red" one |
ILoadAOne(a string) |
the result is 1.5 |
TheResultIs(a *apd.Decimal) |
I see the following + table |
ISeeTheFollowing(t gocuke.DataTable) |
When a step has no matching method, gocuke prints a copy-pasteable suggestion with the correct signature.
Manual step registration
For complex patterns or when auto-discovery doesn't fit:
gocuke.NewRunner(t, &suite{}).
Step(`^I send "(GET|POST|PUT|DELETE)" to "([^"]*)"$`, (*suite).ISendTo).
Step(regexp.MustCompile(`the response code is (\d+)`), (*suite).TheResponseCodeIs).
Run()
Manual registrations take priority over auto-discovered methods.
Injected types
Exported fields of these types on the suite struct are auto-populated:
| Type | What you get |
|---|---|
gocuke.TestingT |
Current *testing.T (or rapid wrapper) |
gocuke.Scenario |
Scenario metadata: Name(), Tags(), URI() |
gocuke.Step |
Current step metadata (nil in hooks) |
*rapid.T |
Rapid handle (triggers property-based mode) |
Data tables
func (s *suite) TheFollowingUsersExist(table gocuke.DataTable) {
// Raw access
table.NumRows() // total rows including header
table.NumCols()
table.Cell(0, 1) // 0-indexed → *Cell
// Header-based access
ht := table.HeaderTable() // first row = column names
ht.NumRows() // rows excluding header
ht.Get(0, "name") // row offset + column name → *Cell
}
Cell methods: .String(), .Int64(), .BigInt(), .Decimal().
Doc strings
func (s *suite) TheConfigContains(doc gocuke.DocString) {
doc.Content // raw string content
doc.MediaType // "json", "yaml", etc. (empty if unannotated)
}
Must be the last parameter.
Hooks
Define methods on the suite struct:
func (s *suite) Before(scenario gocuke.Scenario) { /* per-scenario setup */ }
func (s *suite) After() { /* always runs, even on failure */ }
func (s *suite) BeforeStep() { /* before each step */ }
func (s *suite) AfterStep(step gocuke.Step) { /* after each step */ }
Hooks can also be registered on the runner for shared setup:
gocuke.NewRunner(t, &suite{}).
Before(func() { /* runner-level before */ }).
After(func() { /* runner-level after */ }).
Run()
Property-based testing with rapid
If any step method takes *rapid.T as its first parameter (after the receiver), the entire scenario is wrapped in rapid.Check():
func (s *suite) AnyValidInput(t *rapid.T) {
s.input = rapid.String().Draw(t, "input")
}
- The scenario runs hundreds of times with different random inputs
- On failure, rapid shrinks to a minimal reproducing case
*rapid.Tcan also be an exported field on the suite for universal access
Tag these scenarios @property and use ShortTags("not @property") to skip them during quick iterations.
Tag filtering
// In test code
gocuke.NewRunner(t, &suite{}).
Tags("@smoke"). // always applied
Tags("(@smoke or @unit) and not @slow"). // complex expression
ShortTags("not @property"). // applied with -short
Run()
# From command line (global)
go test ./... -gocuke.tags "not @integration"
go test ./... -short
Running tests
go test ./... # all tests, parallel by default
go test ./... -v # verbose step-by-step output
go test ./... -run TestVCSDetection # single test function
go test ./... -gocuke.tags "@wip" # tag filter
go test ./... -short # trigger ShortTags
go test ./... -count=1 # disable test caching
Each 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.