Skip to content

Instantly share code, notes, and snippets.

@KEINOS
Last active December 15, 2023 05:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KEINOS/76857bc6339515d7144e00f17adb1090 to your computer and use it in GitHub Desktop.
Save KEINOS/76857bc6339515d7144e00f17adb1090 to your computer and use it in GitHub Desktop.
【Golang】How to mock `os.Stdin` during the test in Go. 標準入力をモックする方法

「"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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment