golang实现Jest风格快照测试的插件库go-snaps的使用
Golang实现Jest风格快照测试的插件库go-snaps的使用
安装
使用go get
安装go-snaps
:
go get github.com/gkampitakis/go-snaps
在代码中导入go-snaps/snaps
包:
package example
import (
"testing"
"github.com/gkampitakis/go-snaps/snaps"
)
func TestExample(t *testing.T) {
snaps.MatchSnapshot(t, "Hello World")
}
MatchSnapshot
MatchSnapshot
可以捕获任何类型的结构化或非结构化数据。
你可以传递多个参数给MatchSnapshot
或者在同一个测试中多次调用MatchSnapshot
。区别在于后者会在快照文件中创建多个条目。
// test_simple.go
func TestSimple(t *testing.T) {
t.Run("should make multiple entries in snapshot", func(t *testing.T) {
snaps.MatchSnapshot(t, 5, 10, 20, 25)
snaps.MatchSnapshot(t, "some value")
})
}
go-snaps
将快照保存在__snapshots__
目录中,文件名是测试文件名加上.snap
扩展名。
例如,如果你的测试文件名为test_simple.go
,当你运行测试时,会在./__snapshots__/test_simple.snaps
创建一个快照文件。
MatchStandaloneSnapshot
MatchStandaloneSnapshot
会在单独的文件中创建快照,而MatchSnapshot
会在同一个文件中添加多个快照。
// test_simple.go
func TestSimple(t *testing.T) {
snaps.MatchStandaloneSnapshot(t, "Hello World")
// 或创建一个html快照文件
snaps.WithConfig(snaps.Ext(".html")).
MatchStandaloneSnapshot(t, "<html><body><h1>Hello World</h1></body></html>")
}
go-snaps
将快照保存在__snapshots__
目录中,文件名是t.Name()
加上一个数字和.snap
扩展名。
对于上面的例子,快照文件名将是./__snapshots__/TestSimple_1.snap
和./__snapshots__/TestSimple_1.snap.html
。
MatchJSON
MatchJSON
可用于捕获可以表示有效json的数据。
你可以传递一个有效的json,形式为string
或[]byte
,或者任何可以成功传递给json.Marshal
的值。
func TestJSON(t *testing.T) {
type User struct {
Age int
Email string
}
snaps.MatchJSON(t, `{"user":"mock-user","age":10,"email":"mock@email.com"}`)
snaps.MatchJSON(t, []byte(`{"user":"mock-user","age":10,"email":"mock@email.com"}`))
snaps.MatchJSON(t, User{10, "mock-email"})
}
JSON将以漂亮的格式保存在快照中,以提高可读性和确定性差异。
MatchStandaloneJSON
MatchStandaloneJSON
会在单独的文件中创建快照,而MatchJSON
会在同一个文件中添加多个快照。
func TestSimple(t *testing.T) {
snaps.MatchStandaloneJSON(t, `{"user":"mock-user","age":10,"email":"mock@email.com"}`)
snaps.MatchStandaloneJSON(t, User{10, "mock-email"})
}
go-snaps
将快照保存在__snapshots__
目录中,文件名是t.Name()
加上一个数字和.snap.json
扩展名。
对于上面的例子,快照文件名将是./__snapshots__/TestSimple_1.snap.json
和./__snapshots__/TestSimple_2.snap.json
。
MatchYAML
MatchYAML
可用于捕获可以表示有效yaml的数据。
你可以传递一个有效的yaml,形式为string
或[]byte
,或者任何可以成功传递给yaml.Marshal
的值。
func TestYAML(t *testing.T) {
type User struct {
Age int
Email string
}
snaps.MatchYAML(t, "user: \"mock-user\"\nage: 10\nemail: mock@email.com")
snaps.MatchYAML(t, []byte("user: \"mock-user\"\nage: 10\nemail: mock@email.com"))
snaps.MatchYAML(t, User{10, "mock-email"})
}
MatchStandaloneYAML
MatchStandaloneYAML
会在单独的文件中创建快照,而MatchYAML
会在同一个文件中添加多个快照。
func TestSimple(t *testing.T) {
snaps.MatchStandaloneYAML(t, "user: \"mock-user\"\nage: 10\nemail: \"mock@email.com\"")
snaps.MatchStandaloneYAML(t, User{10, "mock-email"})
}
go-snaps
将快照保存在__snapshots__
目录中,文件名是t.Name()
加上一个数字和.snap.yaml
扩展名。
对于上面的例子,快照文件名将是./__snapshots__/TestSimple_1.snap.yaml
和./__snapshots__/TestSimple_2.snap.yaml
。
匹配器
MatchJSON
和MatchYAML
的第三个参数可以接受一个匹配器列表。匹配器是可以作为属性匹配器和测试值的函数。
你可以传递要匹配和测试的属性的路径。
目前go-snaps
有三个内置匹配器:
match.Any
match.Custom
match.Type[ExpectedType]
match.Any
Any匹配器充当任何值的占位符。它将任何目标路径替换为占位符字符串。
Any("user.name")
// 或多个路径
Any("user.name", "user.email")
Any匹配器提供了一些设置选项的方法:
match.Any("user.name").
Placeholder(value). // 允许定义一个不同于默认"<Any Value>"的占位符值
ErrOnMissingPath(bool) // 确定匹配器在路径缺失时是否会出错,默认为true
match.Custom
自定义匹配器允许你带来自己的验证和占位符值。
match.Custom("user.age", func(val any) (any, error) {
age, ok := val.(float64)
if !ok {
return nil, fmt.Errorf("expected number but got %T", val)
}
return "some number", nil
})
如果自定义匹配器返回错误,快照测试将失败并显示该错误。
自定义匹配器提供了一个设置选项的方法:
match.Custom("path",myFunc).
Placeholder(value). // 允许定义一个不同于默认"<Any Value>"的占位符值
ErrOnMissingPath(bool) // 确定匹配器在路径缺失时是否会出错,默认为true
match.Type
Type匹配器评估快照中传递的类型,并将任何目标路径替换为<Type:ExpectedType>
形式的占位符。
match.Type[string]("user.info")
// 或多个路径
match.Type[float64]("user.age", "data.items")
Type匹配器提供了一个设置选项的方法:
match.Type[string]("user.info").
ErrOnMissingPath(bool) // 确定匹配器在路径缺失时是否会出错,默认为true
配置
go-snaps
允许传递配置来覆盖:
- 存储快照的目录,相对或绝对路径
- 存储快照的文件名
- 快照文件的扩展名(无论扩展名如何,文件名都将在文件名中包含
.snaps
) - 以编程方式控制是否更新快照
- json配置的json格式配置:
Width
: 换行json输出前的最大字符宽度(默认:80)Indent
: 用于嵌套结构的缩进字符串(默认:1个空格)SortKeys
: 是否按字母顺序排序json对象键(默认:true)
t.Run("snapshot tests", func(t *testing.T) {
snaps.WithConfig(snaps.Filename("my_custom_name"), snaps.Dir("my_dir")).MatchSnapshot(t, "Hello Word")
s := snaps.WithConfig(
snaps.Dir("my_dir"),
snaps.Filename("json_file"),
snaps.Ext(".json"),
snaps.Update(false),
snaps.JSON(snaps.JSONConfig{
Width: 80,
Indent: " ",
SortKeys: false,
}),
)
s.MatchJSON(t, `{"hello":"world"}`)
})
更新快照
你可以通过将UPDATE_SNAPS
环境变量设置为true来更新失败的快照。
UPDATE_SNAPS=true go test ./...
如果你不想更新所有失败的快照,或者只想更新其中一个快照,你可以使用-run
标志来定位你想要的测试。
清理过时的快照
go-snaps
可以识别过时的快照。
要启用此功能,你需要在测试运行后使用TestMain(m *testing.M)
调用snaps.Clean(t)
。这也会打印一个快照摘要(如果使用详细标志-v
运行测试)。
如果你想删除过时的快照文件和快照,你可以运行带有UPDATE_SNAPS=clean
环境变量的测试。
使用TestMain
的原因是go-snaps
需要确保所有测试都已完成,以便它可以跟踪哪些快照未被调用。
示例:
func TestMain(m *testing.M) {
v := m.Run()
// 在所有测试运行后,`go-snaps`可以检查未使用的快照
snaps.Clean(m)
os.Exit(v)
}
排序快照
默认情况下,go-snaps
将新快照附加到快照文件中,在并行测试的情况下顺序是随机的。如果你希望快照按确定性顺序排序,你需要为每个包使用TestMain
:
func TestMain(m *testing.M) {
v := m.Run()
// 在所有测试运行后,`go-snaps`将对快照进行排序
snaps.Clean(m, snaps.CleanOpts{Sort: true})
os.Exit(v)
}
跳过测试
如果你想使用t.Skip
跳过一个测试,go-snaps
无法跟踪测试是被跳过还是被移除。因此,go-snaps
暴露了t.Skip
、t.Skipf
和t.SkipNow
的包装器,它们会跟踪跳过的文件。
你可以通过使用-run
标志来跳过或仅运行特定的测试。go-snaps
可以识别哪些测试被跳过,并仅解析相关测试以查找过时的快照。
在CI上运行测试
当go-snaps
检测到它在CI上运行时,如果快照缺失或有差异,它将自动失败。这是为了确保新的快照与测试一起提交,并且断言成功。
你可以通过将UPDATE_SNAPS
设置为always
来覆盖此行为,这将创建或更新快照。
go-snaps
使用ciinfo来检测是否在CI环境中运行。
无颜色
go-snaps
支持通过设置NO_COLOR
环境变量为任何值来禁用彩色输出。
NO_COLOR=true go test ./...
快照结构
快照的形式为:
[TestName - Number]
<data>
---
TestID
是测试名称加上一个递增的数字,以允许在单个测试中多次调用MatchSnapshot
。
[TestSimple/should_make_a_map_snapshot - 1]
map[string]interface{}{
"mock-0": "value",
"mock-1": int(2),
"mock-2": func() {...},
"mock-3": float32(10.399999618530273),
}
---
注意:如果你的快照数据在一行的开头包含字符
---
后跟一个新行,go-snaps
将"转义"它们并将它们保存为/-/-/-/
,以将它们与终止字符区分开来。
已知限制
- 当通过指定路径运行特定的测试文件
go test ./my_test.go
时,go-snaps
无法跟踪路径,因此会错误地将快照标记为过时。 - go-snaps不处理CRLF行尾。如果你使用Windows,你可能需要将行尾转换为LF。
- 当使用
go test -trimpath ./...
运行时,go-snaps无法自动确定快照路径。然后它依赖于当前工作目录来定义快照目录。如果这在你的用例中是一个问题,你可以使用snaps.WithConfig(snaps.Dir("/some/absolute/path"))
设置一个绝对路径
致谢
这个库使用了Jest Snapshoting和Cupaloy作为灵感。
- Jest是一个成熟的Javascript测试框架,具有强大的快照功能。
- Cupaloy是一个很棒且简单的Golang快照解决方案。
- 徽标由MariaLetta制作。
更多关于golang实现Jest风格快照测试的插件库go-snaps的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现Jest风格快照测试的插件库go-snaps的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用go-snaps实现Jest风格的快照测试
在Go语言中实现类似Jest风格的快照测试,可以使用go-snaps
这个库。它提供了简单直观的API来创建和比较快照,非常适合组件测试和数据结构的验证。
安装go-snaps
首先安装go-snaps库:
go get github.com/gkampitakis/go-snaps
基本使用示例
package main
import (
"testing"
"github.com/gkampitakis/go-snaps"
)
func TestUserStruct(t *testing.T) {
user := struct {
Name string
Age int
Email string
}{
Name: "John Doe",
Age: 30,
Email: "john@example.com",
}
// 创建或比较快照
snaps.MatchSnapshot(t, user)
}
主要功能
1. 基本快照测试
func TestBasicTypes(t *testing.T) {
t.Run("test string", func(t *testing.T) {
snaps.MatchSnapshot(t, "hello world")
})
t.Run("test number", func(t *testing.T) {
snaps.MatchSnapshot(t, 42)
})
t.Run("test slice", func(t *testing.T) {
snaps.MatchSnapshot(t, []string{"go", "rust", "python"})
})
}
2. 结构化数据测试
func TestComplexStructure(t *testing.T) {
data := map[string]interface{}{
"user": struct {
ID int
Name string
Roles []string
}{
ID: 1,
Name: "Alice",
Roles: []string{"admin", "user"},
},
"settings": map[string]bool{
"notifications": true,
"dark_mode": false,
},
}
snaps.MatchSnapshot(t, data)
}
3. 测试配置
可以在TestMain
中配置全局行为:
func TestMain(m *testing.M) {
v := m.Run()
// 在测试结束后检查是否有未使用的快照
snaps.Clean(m, snaps.CleanOpts{AfterTestRun: true})
os.Exit(v)
}
高级用法
1. 自定义快照名称
func TestWithCustomName(t *testing.T) {
result := "custom snapshot content"
snaps.MatchSnapshot(t, result, "custom_snapshot_name")
}
2. 更新快照模式
当需要更新快照时,可以设置环境变量:
UPDATE_SNAPS=true go test ./...
或者在代码中控制:
func TestWithUpdate(t *testing.T) {
t.Setenv("UPDATE_SNAPS", "true")
// 现在运行测试会更新快照而不是比较
snaps.MatchSnapshot(t, "updated content")
}
3. 忽略特定字段
func TestWithIgnoredFields(t *testing.T) {
user := struct {
Name string
Age int
Created time.Time // 这个字段我们希望忽略
}{
Name: "Bob",
Age: 25,
Created: time.Now(),
}
// 忽略Created字段
snaps.MatchSnapshot(t, user, snaps.WithIgnoreFields("Created"))
}
最佳实践
-
快照文件管理:
- 快照文件默认存储在
__snapshots__
目录中 - 应该将这些文件提交到版本控制中
- 快照文件默认存储在
-
测试稳定性:
- 避免在快照中包含动态数据(如时间戳、随机数)
- 对于不可避免的动态数据,使用
WithIgnoreFields
忽略它们
-
测试粒度:
- 每个测试用例应该有明确的快照
- 避免一个快照包含太多不相关的数据
-
CI集成:
- 在CI环境中确保不设置
UPDATE_SNAPS
环境变量 - 快照测试失败应该导致构建失败
- 在CI环境中确保不设置
与标准测试库的比较
相比标准库的testing
包,go-snaps
提供了:
- 更简单的数据结构验证方式
- 更好的差异可视化
- 自动快照更新功能
- 更直观的错误报告
总结
go-snaps
为Go语言带来了类似Jest的快照测试体验,特别适合验证复杂数据结构、API响应和组件渲染输出。它的简单API和强大功能可以显著减少编写和维护测试代码的工作量。
通过合理使用快照测试,你可以快速构建可靠的测试套件,同时保持测试代码的简洁性和可维护性。