KEINOS' Blog

Official blog of KEINOS but mostly for my own reference. A Japanese made in Mexico with Mexican quality. Who monkey around the jungle of codes. ;-)


Project maintained by KEINOS Hosted on GitHub Pages — Theme by mattgraham

GitHub Gist stars

「”golang” test “os.Stdin” “keinos”」でググってもヒットしなかったので、自分のググラビリティとして。

[Golang] How to mock/mimic os.Stdin during the test in Go

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
}

Usage

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
}