Go testing patterns for Gentleman.Dots, including Bubbletea TUI testing. Trigger: When writing Go tests, using teatest, or adding test coverage.
60
70%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./skills/go-testing/SKILL.mdUse this skill when:
Standard Go pattern for multiple test cases:
func TestSomething(t *testing.T) {
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{
name: "valid input",
input: "hello",
expected: "HELLO",
wantErr: false,
},
{
name: "empty input",
input: "",
expected: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ProcessInput(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
return
}
if result != tt.expected {
t.Errorf("got %q, want %q", result, tt.expected)
}
})
}
}Test Model state transitions directly:
func TestModelUpdate(t *testing.T) {
m := NewModel()
// Simulate key press
newModel, _ := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
m = newModel.(Model)
if m.Screen != ScreenMainMenu {
t.Errorf("expected ScreenMainMenu, got %v", m.Screen)
}
}Use Charmbracelet's teatest for TUI testing:
func TestInteractiveFlow(t *testing.T) {
m := NewModel()
tm := teatest.NewTestModel(t, m)
// Send keys
tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
tm.Send(tea.KeyMsg{Type: tea.KeyDown})
tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
// Wait for model to update
tm.WaitFinished(t, teatest.WithDuration(time.Second))
// Get final model
finalModel := tm.FinalModel(t).(Model)
if finalModel.Screen != ExpectedScreen {
t.Errorf("wrong screen: got %v", finalModel.Screen)
}
}Compare output against saved "golden" files:
func TestOSSelectGolden(t *testing.T) {
m := NewModel()
m.Screen = ScreenOSSelect
m.Width = 80
m.Height = 24
output := m.View()
golden := filepath.Join("testdata", "TestOSSelectGolden.golden")
if *update {
os.WriteFile(golden, []byte(output), 0644)
}
expected, _ := os.ReadFile(golden)
if output != string(expected) {
t.Errorf("output doesn't match golden file")
}
}Testing a function?
├── Pure function? → Table-driven test
├── Has side effects? → Mock dependencies
├── Returns error? → Test both success and error cases
└── Complex logic? → Break into smaller testable units
Testing TUI component?
├── State change? → Test Model.Update() directly
├── Full flow? → Use teatest.NewTestModel()
├── Visual output? → Use golden file testing
└── Key handling? → Send tea.KeyMsg
Testing system/exec?
├── Mock os/exec? → Use interface + mock
├── Real commands? → Integration test with --short skip
└── File operations? → Use t.TempDir()func TestCursorNavigation(t *testing.T) {
tests := []struct {
name string
startPos int
key string
endPos int
numOptions int
}{
{"down from 0", 0, "j", 1, 5},
{"up from 1", 1, "k", 0, 5},
{"down at bottom", 4, "j", 4, 5}, // stays at bottom
{"up at top", 0, "k", 0, 5}, // stays at top
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewModel()
m.Cursor = tt.startPos
// Set up options...
newModel, _ := m.Update(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune(tt.key),
})
m = newModel.(Model)
if m.Cursor != tt.endPos {
t.Errorf("cursor = %d, want %d", m.Cursor, tt.endPos)
}
})
}
}func TestScreenTransitions(t *testing.T) {
tests := []struct {
name string
startScreen Screen
action tea.Msg
expectScreen Screen
}{
{
name: "welcome to main menu",
startScreen: ScreenWelcome,
action: tea.KeyMsg{Type: tea.KeyEnter},
expectScreen: ScreenMainMenu,
},
{
name: "escape from OS select",
startScreen: ScreenOSSelect,
action: tea.KeyMsg{Type: tea.KeyEsc},
expectScreen: ScreenMainMenu,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := NewModel()
m.Screen = tt.startScreen
newModel, _ := m.Update(tt.action)
m = newModel.(Model)
if m.Screen != tt.expectScreen {
t.Errorf("screen = %v, want %v", m.Screen, tt.expectScreen)
}
})
}
}func TestExerciseValidation(t *testing.T) {
exercise := &Exercise{
Solutions: []string{"w", "W", "e"},
Optimal: "w",
}
tests := []struct {
input string
valid bool
optimal bool
}{
{"w", true, true},
{"W", true, false},
{"e", true, false},
{"x", false, false},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
valid := ValidateAnswer(exercise, tt.input)
optimal := IsOptimalAnswer(exercise, tt.input)
if valid != tt.valid {
t.Errorf("valid = %v, want %v", valid, tt.valid)
}
if optimal != tt.optimal {
t.Errorf("optimal = %v, want %v", optimal, tt.optimal)
}
})
}
}func TestWithMockedSystem(t *testing.T) {
m := NewModel()
// Mock system info for testing
m.SystemInfo = &system.SystemInfo{
OS: system.OSMac,
IsARM: true,
HasBrew: true,
HomeDir: t.TempDir(),
}
// Now test with controlled environment
m.SetupInstallSteps()
// Verify expected steps
hasHomebrew := false
for _, step := range m.Steps {
if step.ID == "homebrew" {
hasHomebrew = true
}
}
if hasHomebrew {
t.Error("should not have homebrew step when HasBrew=true")
}
}installer/internal/tui/
├── model.go
├── model_test.go # Model tests
├── update.go
├── update_test.go # Update handler tests
├── view.go
├── view_test.go # View rendering tests
├── teatest_test.go # Teatest integration tests
├── comprehensive_test.go # Full flow tests
├── testdata/
│ ├── TestOSSelectGolden.golden
│ └── TestViewGolden.golden
└── trainer/
├── types.go
├── types_test.go
├── exercises.go
├── exercises_test.go
└── simulator_test.gogo test ./... # Run all tests
go test -v ./internal/tui/... # Verbose TUI tests
go test -run TestNavigation # Run specific test
go test -cover ./... # With coverage
go test -update ./... # Update golden files
go test -short ./... # Skip integration testsinstaller/internal/tui/*_test.goinstaller/internal/tui/trainer/*_test.goinstaller/internal/system/*_test.goinstaller/internal/tui/testdata/6901875
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.