byob-go-cli

Test prompter is a scripted FIFO stub

byob-prompter.4 promptertesting

Problem: testing code that prompts the user typically involves fragile input-simulation (writing to a pipe, racing a goroutine) or a mocking library that hides the call sequence.

Idea: a Stub type that holds per-method FIFOs. Each call pops the next value. Over-consumption panics (loud failure beats silent incorrect answer). Ordering matches the call order in the test — if the test expects "Confirm, then Input, then Select", the stub's slices are sized accordingly.

Aligns with byob-testing.1 (test doubles through the Factory, no monkey-patching) — the stub replaces f.Prompter directly; no library-level patching is needed.

Tradeoffs: when a command changes the order of its prompts, the test slice doesn't auto-resize — you update the slice, which is exactly the right level of refactor pain. For commands that branch the prompt order based on prior answers, the stub's per-method FIFOs are not order-preserving across methods; write a record-calls helper if that matters.

Design

// pkg/cmd/prompt/stub.go
package prompt

type Stub struct {
    Confirms     []bool
    Inputs       []string
    Passwords    []string
    Selects      []int
    MultiSelects [][]int
}

func (s *Stub) Confirm(_ context.Context, msg string, def bool) (bool, error) {
    if len(s.Confirms) == 0 {
        panic("prompt.Stub: no Confirms left for " + msg)
    }
    v := s.Confirms[0]; s.Confirms = s.Confirms[1:]
    return v, nil
}
// Input/Password/Select/MultiSelect identical shape — each takes
// context.Context as the first param to satisfy the interface
// (byob-prompter.1) and ignores it; tests don't cancel mid-call.

Used in tests:

f := &Factory{
    IOStreams: iostreams.Test(),
    Prompter:  &prompt.Stub{Confirms: []bool{true}, Inputs: []string{"foo"}},
}