Home > Software engineering >  How to test nested input in Go
How to test nested input in Go

Time:12-18

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.

  • Related