gocuke-api.md

gocuke API reference

Contents

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:

  1. Split step text on spaces
  2. Skip numeric and quoted tokens (they become parameters)
  3. PascalCase remaining words, strip punctuation
  4. 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.T can 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.