I have function for getting user input using os.Stdin
func (i input) GetInput(stdin io.Reader) (string, error) {
reader := bufio.NewReader(stdin)
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
In my programm I need to have 2 inputs:
- First for getting base info for example user name
- Second for getting aditional info that depends on first input
name,err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
switch name {
case "test":
//do something...
age, err := GetInput(os.Stdin)
if err != nil {
// error handling.....
}
fmt.Println(age)
case "another":
// Here another input
}
It it possible to write unit tests for that case? For testing one user input I use this snippet and it works:
var stdin bytes.Buffer
stdin.Write([]byte(fmt.Sprintf("%s\n", tt.input)))
GetInput(stdin)
But it didn't work with 2 nested inputs
CodePudding user response:
Maybe consider having a function that returns a specific type as a result and put it into a separate package.
Since I see name
and age
mentioned, perhaps we can assume a concrete type like Person
for illustration.
It is important to note that we want to include the actual reader as a parameter and not have a hard coded reference to os.Stdin
. This makes the mocking of nested inputs possible in the first place.
With this, the signature of the method could look something like the following:
func NestedInput(input io.Reader) (*Person, error)
The corresponding type could be:
type Person struct {
Name string
Age int
}
If one now combines your code snippets to a complete GO file with the name input.go
in a separate directory, it might look something like this:
package input
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
func getInput(reader *bufio.Reader) (string, error) {
data, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("get input error: %w", err)
}
return strings.ReplaceAll(data, "\n", ""), nil
}
type Person struct {
Name string
Age int
}
func NestedInput(input io.Reader) (*Person, error) {
reader := bufio.NewReader(input)
name, err := getInput(reader)
if err != nil {
return nil, err
}
switch name {
case "q":
return nil, nil
default:
ageStr, err := getInput(reader)
if err != nil {
return nil, err
}
age, err := strconv.Atoi(ageStr)
if err != nil {
return nil, err
}
return &Person{Name: name, Age: age}, nil
}
}
An input of q
returns nil, nil
and could be used to terminate the input, e.g. if the query was made in a loop.
Unit Test
The unit test
func Test_nestedInput(t *testing.T)
in a file named input_test.go
should now provide the input data.
Since the NestedInput
function now expects an io.Reader
as a parameter, we can simply generate the desired input with, for example,
input := strings.NewReader("George\n26\n")
So the test could look something like this:
package input
import (
"strings"
"testing"
)
func Test_nestedInput(t *testing.T) {
input := strings.NewReader("George\n26\n")
person, err := NestedInput(input)
if err != nil {
t.Error("nested input failed")
}
if person == nil {
t.Errorf("expected person, but got nil")
return
}
if person.Name != "George" {
t.Errorf("wrong name %s, expected 'George'", person.Name)
}
if person.Age != 26 {
t.Errorf("wrong age %d, expected 26", person.Age)
}
}
Of course, the tests can be extended with further details. But this, as you can see, mocks a nested input.