fstest.mapfs 不能直接写入,因为它是只读的,仅实现 fs.fs 而非 fs.readwritefs,调用写操作会 panic;适合只读测试场景,写入需换用 memfs 或 afero 等可写内存文件系统。

fstest.MapFS 为什么不能直接写入
因为 fstest.MapFS 是只读的——它本质是 map[string]fs.FileInfo 加一层封装,没有实现 fs.ReadWriteFS 接口。你调用 OpenFile(..., os.O_WRONLY, 0) 会直接 panic:"not implemented"。
常见错误现象:在测试中 mock 文件写入逻辑,却用 fstest.MapFS 构造初始数据后尝试 os.WriteFile 或 ioutil.WriteFile,结果报错或静默失败。
- 只读场景(如测试读配置、遍历模板)用
fstest.MapFS完全够用 - 需要写入 + 读取双向操作,必须换方案,比如
memfs.New(第三方)或自己实现fs.FS+fs.ReadFileFS组合 - 若只是想“假装写了”,可在 map 中预置新键值对,但注意:这不等于运行时写入,只是构造时静态定义
如何让测试代码真正读写内存文件系统
Go 标准库没提供可写内存 FS,得靠组合或轻量第三方。推荐用 github.com/spf13/afero(成熟)或 github.com/kevin-cantwell/memfs(纯标准库依赖)。
以 memfs 为例,它返回的是 fs.FS + 可导出的 *memfs.FS 实例,支持 Create、Remove、Open 等完整操作:
立即学习“go语言免费学习笔记(深入)”;
import "github.com/kevin-cantwell/memfs"
fs := memfs.New()
f, _ := fs.Create("config.json")
f.Write([]byte(`{"debug": true}`))
f.Close()
data, _ := fs.ReadFile("config.json") // 能读到刚写的
-
memfs.FS实现了fs.ReadWriteFS和fs.StatFS,可直接传给接受fs.FS参数的函数 - 避免用
os.OpenFile操作它——那是针对真实 OS 文件句柄的,要改用fs.Open或fs.ReadFile - 注意:它的
Stat返回的ModTime默认是零值,某些依赖时间戳的逻辑可能需要 patch
fstest.MapFS 在 testing 中的正确用法
它唯一正经用途是验证「只读路径逻辑」是否兼容 fs.FS 接口,比如 http.FileServer、template.ParseFS、embed.FS 的替代测试。
典型场景:你有一段代码从 fs.FS 读取模板并渲染,不想依赖磁盘文件——这时 fstest.MapFS 就是标准答案:
fs := fstest.MapFS{
"tmpl/base.html": &fstest.MapFile{Data: []byte("{{.Name}}")},
"tmpl/page.html": &fstest.MapFile{Data: []byte("Hello {{.Name}}")},
}
t, _ := template.ParseFS(fs, "tmpl/*.html")
t.Execute(os.Stdout, struct{ Name string }{"Alice"})
- 键名必须是正斜杠分隔的路径(如
"a/b/c.txt"),不能含..或开头/ -
fstest.MapFile的Mode字段影响fs.IsDir判断,目录需设为0o755 | fs.ModeDir - 别试图对它调用
fs.Remove或fs.Create,那些方法都 panic,不是 bug 是设计如此
嵌入式文件系统 embed.FS 和 fstest.MapFS 的关系
embed.FS 是编译期打包进二进制的只读 FS,而 fstest.MapFS 是运行时构建的只读 FS,二者接口一致,可以互换用于测试。
如果你用 //go:embed 打包了资源,又想在单元测试里模拟相同结构,最稳妥方式是:把 embed.FS 的内容 dump 成 MapFS:
func toMapFS(fsys embed.FS) fstest.MapFS {
m := make(fstest.MapFS)
for _, p := range mustReadDir(fsys, ".") {
data, _ := fsys.ReadFile(p.Name())
m[p.Name()] = &fstest.MapFile{Data: data, Mode: p.Type()}
}
return m
}
-
embed.FS不支持ReadDir直接遍历,得用fs.WalkDir或先列出已知路径 - 这种转换仅适合小规模资源;大文件或大量路径会导致测试启动变慢
- 注意权限和 ModTime 在 embed 中是固定值,
fstest.MapFile需手动赋值,否则默认为 0
fstest.MapFS,只读验证就别去折腾 memfs。接口类型擦除得很干净,但底层行为不会骗人。










