Backend Development 12 min read

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.

Go Programming World
Go Programming World
Go Programming World
Handling External File Dependencies in Go Unit Tests with Temporary Files and embed

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.

Gounit testingembedtemporary filesFile Dependencyos.CreateTemp
Go Programming World
Written by

Go Programming World

Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.