Skip to content

Commit bd93a8b

Browse files
Refactor coin toss input handling to use a mockable prompter interface and add unit tests for user interaction
1 parent 4a1bb02 commit bd93a8b

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

internal/cointoss/cointoss.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import (
77
"strings"
88
"time"
99

10-
ghprompt "github.com/cli/go-gh/v2/pkg/prompter"
10+
userPrompt "github.com/cli/go-gh/v2/pkg/prompter"
1111
)
1212

13+
// prompter interface allows us to mock the prompt functionality in tests
14+
type prompter interface {
15+
Select(prompt string, defaultValue string, options []string) (int, error)
16+
}
17+
1318
func TossCoin() string {
1419
rand.Seed(time.Now().UnixNano())
1520
if rand.Float32() < 0.5 {
@@ -27,8 +32,11 @@ func ValidateGuess(guess string) error {
2732
}
2833

2934
func GetNextGuess() (string, bool) {
35+
return GetNextGuessWithPrompter(userPrompt.New(os.Stdin, os.Stdout, os.Stderr))
36+
}
37+
38+
func GetNextGuessWithPrompter(p prompter) (string, bool) {
3039
options := []string{"Heads", "Tails", "Quit"}
31-
p := ghprompt.New(os.Stdin, os.Stdout, os.Stderr)
3240

3341
answer, err := p.Select("What's your next guess? Heads, Tails or Quit?", "Heads", options)
3442
if err != nil {

internal/cointoss/cointoss_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
package cointoss
22

33
import (
4+
"errors"
45
"testing"
56
)
67

8+
// Mock prompter for testing
9+
type mockPrompter struct {
10+
selectAnswer int
11+
selectError error
12+
}
13+
14+
func (m *mockPrompter) Select(prompt string, defaultValue string, options []string) (int, error) {
15+
return m.selectAnswer, m.selectError
16+
}
17+
718
func TestValidateGuess(t *testing.T) {
819
validGuesses := []string{"heads", "heads ", " HEADS", "tails", "TAILS", " tails "}
920
invalidGuesses := []string{"", "foo", "123", "heds", "taol"}
@@ -29,3 +40,67 @@ func TestTossCoin(t *testing.T) {
2940
}
3041
}
3142
}
43+
44+
func TestGetNextGuessWithPrompter(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
selectAnswer int
48+
selectError error
49+
expectedGuess string
50+
expectedCont bool
51+
}{
52+
{
53+
name: "select heads",
54+
selectAnswer: 0,
55+
selectError: nil,
56+
expectedGuess: "heads",
57+
expectedCont: true,
58+
},
59+
{
60+
name: "select tails",
61+
selectAnswer: 1,
62+
selectError: nil,
63+
expectedGuess: "tails",
64+
expectedCont: true,
65+
},
66+
{
67+
name: "select quit",
68+
selectAnswer: 2,
69+
selectError: nil,
70+
expectedGuess: "",
71+
expectedCont: false,
72+
},
73+
{
74+
name: "error during selection",
75+
selectAnswer: 0,
76+
selectError: errors.New("mock error"),
77+
expectedGuess: "",
78+
expectedCont: false,
79+
},
80+
{
81+
name: "error during selection prints error message",
82+
selectAnswer: 0,
83+
selectError: errors.New("test error"),
84+
expectedGuess: "",
85+
expectedCont: false,
86+
},
87+
}
88+
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
mockP := &mockPrompter{
92+
selectAnswer: tt.selectAnswer,
93+
selectError: tt.selectError,
94+
}
95+
96+
guess, cont := GetNextGuessWithPrompter(mockP)
97+
98+
if guess != tt.expectedGuess {
99+
t.Errorf("GetNextGuessWithPrompter() guess = %v, want %v", guess, tt.expectedGuess)
100+
}
101+
if cont != tt.expectedCont {
102+
t.Errorf("GetNextGuessWithPrompter() cont = %v, want %v", cont, tt.expectedCont)
103+
}
104+
})
105+
}
106+
}

0 commit comments

Comments
 (0)