diff --git a/README.md b/README.md index ac2c6fc7..c587b2f6 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,10 @@ survey.AskOne( ## Testing +There are two ways to test a program using survey: + +### Using a simulated terminal + You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/go-expect). The library can be used to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, if you are manipulating the cursor or using `survey`, you will need a way to interpret terminal / ANSI escape sequences @@ -450,6 +454,70 @@ stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x). For some examples, you can see any of the tests in this repo. +### Using a mock (unstable API) + +>**Warning:** +> The Mock API is currently still unstable and subject to change. +> Once it's done, it will be the recommended way to test survey. +> If you are unsure what to use right now, use a simulated terminal. + +Instead of calling the survey functions directly, you can create a survey struct and call the functions from there. + +```golang + +survey := survey.Survey{} + +response := false +prompt := &survey.Confirm{ + Message: "Do you like pie?", +} +survey.AskOne(prompt, &response) + +``` + +If you create only one survey struct at the top level of your program and pass it to all functions, you can test those functions by replacing the struct with a mock provided by survey. + +#### main + +```golang +func main() { + surveyor := survey.Surveyor{} + + AskForPie(surveyor) +} + +func AskForPie(surveyor survey.SurveyInterface) bool { + response := false + prompt := &survey.Confirm{ + Message: "Do you like pie?", + } + surveyor.AskOne(prompt, &response) + + return response +} + +``` + +#### Test + +```golang +func TestAskForPie(t *testing.T) { + + //create mock + mock := survey.SurveyorMock{} + //set the response the "user" should select + mock.setResponse(true) + + result := AskForPie(mock) + + //check output of the function + if !result { + t.Fatal("AskForPie returned false, but it should have returned true") + } +} + +``` + ## FAQ ### What kinds of IO are supported by `survey`? diff --git a/survey.go b/survey.go index 95136ebe..be4af5cd 100644 --- a/survey.go +++ b/survey.go @@ -12,6 +12,22 @@ import ( "github.com/AlecAivazis/survey/v2/terminal" ) +//SurveyorInterface includes all the functions a normal user uses when using survey +type SurveyorInterface interface { + AskOne(p Prompt, response interface{}, opts ...AskOpt) error + Ask(qs []*Question, response interface{}, opts ...AskOpt) error +} + +type Surveyor struct{} + +//the following two functions are just passthroughs to the actual functions, but they make sure that the surveyor struct is backwards compatible to calling the functions directly +func (surveyor Surveyor) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + return AskOne(p, response, opts...) +} +func (surveyor Surveyor) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + return Ask(qs, response, opts...) +} + // DefaultAskOptions is the default options on ask, using the OS stdio. func defaultAskOptions() *AskOptions { return &AskOptions{ diff --git a/surveyorMock.go b/surveyorMock.go new file mode 100644 index 00000000..768de7e1 --- /dev/null +++ b/surveyorMock.go @@ -0,0 +1,41 @@ +package survey + +import ( + "github.com/AlecAivazis/survey/v2/core" +) + +type SurveyorMock struct { + singleResponse interface{} + multipleResponses map[string]interface{} +} + +func (mock *SurveyorMock) AskOne(p Prompt, response interface{}, opts ...AskOpt) error { + err := core.WriteAnswer(response, "", mock.singleResponse) + if err != nil { + // panicing is fine inside a mock + panic(err) + } + return nil +} + +func (mock *SurveyorMock) Ask(qs []*Question, response interface{}, opts ...AskOpt) error { + for _, q := range qs { + + err := core.WriteAnswer(response, q.Name, mock.multipleResponses[q.Name]) + if err != nil { + // panicing is fine inside a mock + panic(err) + } + + } + + return nil +} + +func (mock *SurveyorMock) SetResponse(response interface{}) { + if val, ok := response.(map[string]interface{}); ok { + mock.multipleResponses = val + } else { + mock.singleResponse = response + } +} diff --git a/surveyorMock_test.go b/surveyorMock_test.go new file mode 100644 index 00000000..11966c85 --- /dev/null +++ b/surveyorMock_test.go @@ -0,0 +1,53 @@ +package survey + +import ( + "testing" +) + +func TestMockAskOne(t *testing.T) { + t.Run("Setting Response", func(t *testing.T) { + mock := SurveyorMock{} + mock.SetResponse(true) + + prompt := &Confirm{ + Message: "test", + } + + var response bool + mock.AskOne(prompt, &response) + + if !response { + t.Fatalf("Response was false but should have been true!") + } + }) + +} + +func TestMockAsk(t *testing.T) { + t.Run("Setting Response", func(t *testing.T) { + mock := SurveyorMock{} + + test := make(map[string]interface{}) + test["test"] = true + + mock.SetResponse(test) + + questions := []*Question{ + { + Name: "test", + Prompt: &Confirm{ + Message: "testing", + }, + }, + } + + answer := struct { + Test bool + }{} + mock.Ask(questions, &answer) + + if !answer.Test { + t.Fatalf("Response was false but should have been true!") + } + }) +}