Open-Source Wikis

/

GitHub CLI

/

How to contribute

/

Testing

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. Use require.NoError(t, err) and require.Error(t, err) so failures halt the test immediately. Use assert.Equal for non-fatal equality assertions.
  • Table-driven tests: the dominant style. Each row carries a name plus inputs/expectations. See internal/agents/detect_test.go for a small example and pkg/cmd/issue/list/list_test.go for a larger one.
  • moq: generates mocks for interfaces. The //go:generate directive sits next to the interface. Run go 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:

  1. Flag parsing tests call NewCmdFoo(f, func(opts *FooOptions) error { ...assert opts... }) so the test asserts what the parsed opts look like without ever running the real handler.
  2. Run-function tests build a FooOptions directly, attach a fake IOStreams, a fake httpmock.Registry-backed HTTP client, and a fake Prompter, then call fooRun(opts).
  3. Acceptance tests under acceptance/ build a real bin/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_nontty

Most 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

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – GitHub CLI wiki | Factory