Skip to content

Instantly share code, notes, and snippets.

@KEINOS
Last active October 20, 2024 00:58
Show Gist options
  • 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. 標準入力をモックする方法

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
}
@Aboubakary833
Copy link

Aboubakary833 commented Oct 17, 2024

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 both bytes.Buffer{} and os.Stdin are readers. But I was asking myself if it's a good idea or not.

@KEINOS
Copy link
Author

KEINOS commented Oct 20, 2024

@Aboubakary833 Glad this helped! I was there too! That is what made me write this article!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment