gh-stack config.Config Struct and Test Overrides: Complete Configuration Guide

The config.Config struct centralizes all runtime settings for the gh-stack extension, bundling I/O streams, color formatting helpers, interactive prompt callbacks, and service overrides that enable deterministic testing through dependency injection.

The github/gh-stack repository implements a centralized configuration pattern to manage CLI behavior and facilitate unit testing. Understanding the gh-stack config.Config struct and test overrides is essential for extending functionality or debugging interaction flows. This configuration object aggregates everything from stdout/stderr writers to mockable GitHub client interfaces into a single injectable dependency consumed by all subcommands.

Config Struct Fields Overview

The Config struct defined in internal/config/config.go exposes several field categories that control both runtime behavior and testability.

I/O Streams and Color Formatting

The struct captures output destinations and text styling functions to ensure consistent terminal rendering across platforms:

  • Stdout and Stderr (io.Writer): Destination writers for normal and error output. In production, these map to os.Stdout and os.Stderr, while tests replace them with bytes.Buffer instances via config.NewTestConfig.
  • ColorFn, BoldFn, ErrorFn, InfoFn, SuccessFn, WarningFn (func(string) string): Helper functions that apply ANSI escape codes for terminal styling. Production implementations wrap text in color codes, whereas tests substitute identity functions (func(s string) string { return s }) to produce deterministic output.

Interactive Prompt Hooks

To enable non-interactive testing, the struct includes function fields that replace user input mechanisms:

  • Interactive (bool): Global flag indicating whether the environment supports user interaction. Set to false in CI environments and most test scenarios.
  • SelectFn (func(prompt, defaultValue string, options []string) (int, error)): Replaces the "select one of N options" UI. Tests stub this to return predetermined indices.
  • ConfirmFn (func(prompt string, defaultValue bool) (bool, error)): Hook for yes/no prompts. Test implementations return fixed boolean values.
  • InputFn (func(prompt, defaultValue string) (string, error)): Handles free-form text input. Tests provide deterministic string responses.

Service and Repository Overrides

The struct supports dependency injection for external services:

  • RepoOverride (string): Forces commands to target a specific repository instead of detecting the current working directory's remote. Used extensively in tests with temporary fixture repositories.
  • GitHubClientOverride (github.ClientOps): Swaps the live GitHub API client with a mock implementation (github.MockClient). The config.NewTestConfig helper automatically initializes this field for test configurations.
  • GitOverride (git.Ops): Though the field exists in Config primarily for completeness and is not always stored directly, it allows replacement of git operations with git.MockOps via the global git.SetOps() function.

Internal Testing Flags

  • TestMode (bool): Signals execution under the test harness, enabling additional sanity checks such as strict mock restoration verification.

Test Configuration Patterns

The internal/config/testing.go file provides the NewTestConfig constructor that standardizes test setup across the suite.

Creating Test Configurations

The helper function returns a fully initialized config along with capture buffers:

func NewTestConfig() (*Config, *bytes.Buffer, *bytes.Buffer) {
    stdout := new(bytes.Buffer)
    stderr := new(bytes.Buffer)
    cfg := &Config{
        Stdout:      stdout,
        Stderr:      stderr,
        ColorFn:     func(s string) string { return s },
        BoldFn:      func(s string) string { return s },
        Interactive: false,
    }
    return cfg, stdout, stderr
}

This implementation disables color formatting and interactivity by default, ensuring tests produce stable, parseable output regardless of terminal capabilities.

Customizing Test Behavior

Individual tests override specific hooks to simulate user interactions:

cfg, stdout, stderr := config.NewTestConfig()

// Force selection of the second option (index 1)
cfg.SelectFn = func(_, _ string, _ []string) (int, error) { 
    return 1, nil 
}

// Always confirm prompts
cfg.ConfirmFn = func(_ string, _ bool) (bool, error) { 
    return true, nil 
}

// Provide deterministic text input
cfg.InputFn = func(_, _ string) (string, error) { 
    return "feature-branch", nil 
}

// Inject mock GitHub client
cfg.GitHubClientOverride = &github.MockClient{
    ListStacksFn: func(_ context.Context, _, _ string) ([]github.Stack, error) {
        return []github.Stack{{ID: "S1", Number: 42}}, nil
    },
}

Implementation in Commands

Command implementations in the cmd/ directory receive *config.Config as their primary dependency. According to cmd/utils.go (lines 1420-1430 and 1760-1770), commands check whether prompt hooks are nil before invoking them, falling back to real interactive implementations when running in production.

For example, a command consuming the configuration:

func runAdd(cfg *config.Config, args []string) error {
    // Use configured stdout
    fmt.Fprintln(cfg.Stdout, "Processing...")
    
    // Check if running in interactive mode
    if cfg.Interactive && cfg.SelectFn != nil {
        idx, err := cfg.SelectFn("Choose stack", "", []string{"A", "B"})
        if err != nil {
            return err
        }
        // process selection...
    }
    
    // Use potentially mocked GitHub client
    client := cfg.GitHubClientOverride
    if client == nil {
        // initialize real client
    }
}

Key Source File Locations

Understanding the complete configuration system requires examining these specific files in the github/gh-stack repository:

Summary

  • config.Config aggregates I/O writers, color helpers, prompt callbacks, and service overrides into a single injectable configuration object defined in internal/config/config.go.
  • Test overrides include NewTestConfig for standardized setup, buffer-based stdout/stderr capture, identity color functions, and mockable client interfaces.
  • Interactive hooks (SelectFn, ConfirmFn, InputFn) allow deterministic simulation of user input without actual terminal interaction.
  • Service overrides (GitHubClientOverride, RepoOverride) enable testing against mock GitHub API responses and temporary fixture repositories.
  • Source locations span internal/config/config.go, internal/config/testing.go, and cmd/utils.go for the core configuration logic.

Frequently Asked Questions

How do I disable color output when testing gh-stack commands?

Set the color helper fields to identity functions that return strings unchanged. The config.NewTestConfig helper does this automatically by assigning func(s string) string { return s } to ColorFn, BoldFn, and other styling fields in internal/config/testing.go, producing plain text suitable for assertions.

What is the purpose of GitHubClientOverride in the Config struct?

GitHubClientOverride allows tests to substitute the live GitHub API client with a github.MockClient implementation. This field accepts any type satisfying the github.ClientOps interface, enabling deterministic testing of stack operations without network calls or authentication requirements.

How does the Interactive field affect command execution?

When Interactive is false, commands skip user prompts entirely or use default values, making the tool suitable for CI pipelines. In test environments, NewTestConfig sets this to false by default, while individual tests can combine it with stubbed SelectFn or ConfirmFn functions to simulate specific user choices programmatically.

Where are the mock implementations for git operations defined?

The internal/git/mock_ops.go file defines MockOps, which implements the git operation interface used throughout gh-stack. While Config contains fields for service overrides, git operations are typically replaced using git.SetOps(&git.MockOps{...}) in test setup, allowing commands to execute against synthetic repository states without modifying the filesystem.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →