cli/cli
Testing
The repo's test culture is strict but consistent: every command has a _test.go next to it, every HTTP call is mocked, every prompt is mocked, and tests run quickly enough that go test ./... is the default.
Frameworks
testing+testify: standard for assertions. Userequire.NoError(t, err)andrequire.Error(t, err)so failures halt the test immediately. Useassert.Equalfor non-fatal equality assertions.- Table-driven tests: the dominant style. Each row carries a
nameplus inputs/expectations. Seeinternal/agents/detect_test.gofor a small example andpkg/cmd/issue/list/list_test.gofor a larger one. moq: generates mocks for interfaces. The//go:generatedirective sits next to the interface. Rungo generate ./...after changes.
HTTP mocking
pkg/httpmock is used everywhere. The pattern is:
reg := &httpmock.Registry{}
defer reg.Verify(t)
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO"),
httpmock.JSONResponse(someStruct),
)
reg.Register(
httpmock.GraphQL(`query PullRequestList\b`),
httpmock.FileResponse("./fixtures/prList.json"),
)
client := &http.Client{Transport: reg}reg.Verify(t) asserts every registered stub was called exactly once, which catches dead matchers when refactors leave a stub orphaned.
Common matchers and responders live in pkg/httpmock/registry.go and pkg/httpmock/stub.go (REST, GraphQL, JSONResponse, FileResponse, StringResponse, StatusStringResponse, WithHeader, ...).
Terminal mocking
ios, stdin, stdout, stderr := iostreams.Test()
ios.SetStdoutTTY(true) // simulate "is a terminal"iostreams.Test() returns an IOStreams whose buffers are inspectable, plus the three buffer pointers for assertions. Set SetStdoutTTY / SetStderrTTY / SetStdinTTY to choose between TTY and non-TTY rendering paths.
Prompter mocking
The Prompter interface in internal/prompter has a generated moq mock at internal/prompter/prompter_mock.go. Tests construct an &prompter.PrompterMock{...} with implementations for the prompts the test expects to fire, then assert via the mock's Calls().
Wiring it together
Most command tests fall into one of three shapes:
- Flag parsing tests call
NewCmdFoo(f, func(opts *FooOptions) error { ...assert opts... })so the test asserts what the parsedoptslook like without ever running the real handler. - Run-function tests build a
FooOptionsdirectly, attach a fakeIOStreams, a fakehttpmock.Registry-backed HTTP client, and a fakePrompter, then callfooRun(opts). - Acceptance tests under
acceptance/build a realbin/gh, set up a fake config and credentials, and exercise the binary end-to-end against fixtures. They are gated behind-tags acceptance.
JSON field tests
Commands that opt into --json flags use cmdutil.AddJSONFlags. The shared exporter (cmdutil.Exporter) is exercised by pkg/jsonfieldstest, which asserts that every documented field is actually exported from the relevant struct. Add new fields to both the exporter and the field test.
Running a single test
go test ./pkg/cmd/issue/list/... -run TestIssueList_nonttyMost runs complete in well under a minute. The big offenders are pkg/cmd/run/view, pkg/cmd/pr/create, and pkg/cmd/skills/install; they have ~100 KB test files each.
Where the canonical examples live
- Command implementation + tests:
pkg/cmd/issue/list/list.goandlist_test.go. - Pure unit tests:
internal/agents/detect_test.go. - Factory wiring:
pkg/cmd/factory/default_test.go. - Acceptance harness:
acceptance/README.md.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.