「"golang" test "os.Stdin" "keinos"」でググってもヒットしなかったので、自分のググラビリティとして。
In the dependency injection point of view, it is a good practice to var OsStdin = os.Stdin
and use OsStdin
instead of os.Stdin
. Then monkey patch (temporary replace) the variable during the test.
But if the external package doesn't support that OsStdin
alias feature, and uses os.Stdin
, we need to mock the os.Stdin
some how.
Here's my snippet of the helper function to mock the os.Stdin
.
// mockStdin is a helper function that lets the test pretend dummyInput as os.Stdin.
// It will return a function for `defer` to clean up after the test.
//
// Note: `ioutil.TempFile` should be replaced to `os.CreateTemp` for Go v1.16 or higher.
func mockStdin(t *testing.T, dummyInput string) (funcDefer func(), err error) {
t.Helper()
oldOsStdin := os.Stdin
tmpfile, err := ioutil.TempFile(t.TempDir(), t.Name())
if err != nil {
return nil, err
}
content := []byte(dummyInput)
if _, err := tmpfile.Write(content); err != nil {
return nil, err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return nil, err
}
// Set stdin to the temp file
os.Stdin = tmpfile
return func() {
// clean up
os.Stdin = oldOsStdin
os.Remove(tmpfile.Name())
}, nil
}
package main
import (
"bufio"
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ----------------------------------------------------------------------------
// The Target Function
// ----------------------------------------------------------------------------
// ReadFromSTDIN returns a string read from STDIN. Which you can not edit.
func ReadFromSTDIN() (string, error) {
var stdin []byte
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
stdin = append(stdin, scanner.Bytes()...)
}
if err := scanner.Err(); err != nil {
return "", err
}
return string(stdin), nil
}
// ----------------------------------------------------------------------------
// The Test
// ----------------------------------------------------------------------------
func TestReadFromSTDIN(t *testing.T) {
userInput := "this is my dummy input 123"
funcDefer, err := mockStdin(t, userInput)
if err != nil {
t.Fatal(err)
}
defer funcDefer()
expect := "this is my dummy input 123"
actual, err := ReadFromSTDIN()
require.NoError(t, err)
assert.Equal(t, expect, actual)
}
// ----------------------------------------------------------------------------
// Helper Functions
// ----------------------------------------------------------------------------
// mockStdin is a helper function that lets the test pretend dummyInput as os.Stdin.
// It will return a function to `defer` clean up after the test.
func mockStdin(t *testing.T, dummyInput string) (funcDefer func(), err error) {
t.Helper()
oldOsStdin := os.Stdin
tmpfile, err := ioutil.TempFile(t.TempDir(), t.Name())
if err != nil {
return nil, err
}
content := []byte(dummyInput)
if _, err := tmpfile.Write(content); err != nil {
return nil, err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return nil, err
}
// Set stdin to the temp file
os.Stdin = tmpfile
return func() {
// clean up
os.Stdin = oldOsStdin
os.Remove(tmpfile.Name())
}, nil
}
- View it work online @ Go Playground
- Ref: "Mocking os.Stdin" | Fill os.Stdin for function that reads from it @ StackOverflow
Thank you. I was desperately looking for another means to mock my prompt input. I'm new to Golang. The first solution that came to my mind was the use of
bytes.Buffer{}
to mock the standard input since bothbytes.Buffer{}
andos.Stdin
are readers. But I was asking myself if it's a good idea or not.