Handling External File Dependencies in Go Unit Tests with Temporary Files and embed
This article explains how to resolve external file dependencies in Go unit tests by creating temporary files with os.CreateTemp, initializing global variables, and leveraging the embed package to embed changelog files as []byte or a file system, providing complete code examples and test results.
Modern web applications often use RESTful APIs, reducing direct HTML file handling, but sometimes a file like CHANGELOG.md must be read and returned by an API.
When writing unit tests, such file reads become external dependencies. This article demonstrates how to handle them in Go.
GetChangeLog Example
Assume a function GetChangeLog reads CHANGELOG.md and returns a ChangeLogSpec struct containing version, commit, Go version, and the changelog content.
package main
import (
"io"
"os"
)
var (
version = "dev"
commit = "none"
builtGoVersion = "unknown"
changeLogPath = "CHANGELOG.md"
)
type ChangeLogSpec struct{
Version string
Commit string
BuiltGoVersion string
ChangeLog string
}
func GetChangeLog() (ChangeLogSpec, error) {
data, err := os.ReadFile(changeLogPath)
if err != nil {
return ChangeLogSpec{}, err
}
return ChangeLogSpec{
Version: version,
Commit: commit,
BuiltGoVersion: builtGoVersion,
ChangeLog: string(data),
}, nil
}Testing with Temporary Files
To test GetChangeLog, create a temporary file using os.CreateTemp, write sample changelog data, set changeLogPath to the temp file, and run the test, cleaning up afterwards.
func TestGetChangeLog(t *testing.T) {
// 创建临时文件
// 第一个参数传 "", 表示在操作系统的临时目录下创建该文件
// 文件文件名会以第二个参数作为前缀,剩余的部分会自动生成,以确保并发调用时生成的文件名不重复
f, err := os.CreateTemp("", "TEST_CHANGELOG")
assert.NoError(t, err)
defer func() {
_ = f.Close()
// 尽管操作系统会在某个时间自动清理临时文件,但主动清理是创建者的责任
_ = os.RemoveAll(f.Name())
}()
changeLogPath = f.Name()
data := `
# Changelog
All notable changes to this project will be documented in this file.
`
_, err = f.WriteString(data)
assert.NoError(t, err)
expected := ChangeLogSpec{
Version: "v0.1.1",
Commit: "1",
BuiltGoVersion: "1.20.1",
ChangeLog: `
# Changelog
All notable changes to this project will be documented in this file.
`,
}
actual, err := GetChangeLog()
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}Global variables version, commit, builtGoVersion are also external dependencies; they can be set in init or TestMain.
func init() {
version = "v0.1.1"
commit = "1"
builtGoVersion = "1.20.1"
}Testing with Go embed
Alternatively, embed the changelog file at compile time using //go:embed. The file can be embedded as []byte or as an embed.FS.
embed []byte
Embed the file into a []byte variable and write it to a temporary file during the test.
package main
import (
_ "embed"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
//go:embed testdata/CHANGELOG.md
var changelog []byte
func TestGetChangeLog_by_embed(t *testing.T) {
f, err := os.CreateTemp("", "TEST_CHANGELOG")
assert.NoError(t, err)
defer func() {
_ = f.Close()
_ = os.RemoveAll(f.Name())
}()
changeLogPath = f.Name()
_, err = f.Write(changelog)
assert.NoError(t, err)
expected := ChangeLogSpec{
Version: "v0.1.1",
Commit: "1",
BuiltGoVersion: "1.20.1",
ChangeLog: string(changelog),
}
actual, err := GetChangeLog()
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}embed fs.FS
Embed the file as a file system, open it, and pass the reader to a new GetChangeLogByIOReader function that accepts an io.Reader.
//go:embed testdata/CHANGELOG.md
var fs embed.FS
func TestGetChangeLogByIOReader(t *testing.T) {
f, err := fs.Open("testdata/CHANGELOG.md")
assert.NoError(t, err)
data, err := io.ReadAll(f)
assert.NoError(t, err)
// 将数据的读取位置重置到开头
_, err = f.(io.ReadSeeker).Seek(0, 0)
assert.NoError(t, err)
expected := ChangeLogSpec{
Version: "v0.1.1",
Commit: "1",
BuiltGoVersion: "1.20.1",
ChangeLog: string(data),
}
actual, err := GetChangeLogByIOReader(f)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
} func GetChangeLogByIOReader(reader io.Reader) (ChangeLogSpec, error) {
data, err := io.ReadAll(reader)
if err != nil {
return ChangeLogSpec{}, err
}
return ChangeLogSpec{
Version: version,
Commit: commit,
BuiltGoVersion: builtGoVersion,
ChangeLog: string(data),
}, nil
}Both approaches allow the unit test to run without relying on external file paths.
Summary
The article shows how to use os.CreateTemp and the embed package to eliminate external file dependencies in Go unit tests, providing complete code snippets and test commands.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.