Vitest snapshotSerializers 配置详解:为快照测试注册自定义序列化器
2026/9/14 6:40:06 网站建设 项目流程

Vitest snapshotSerializers 配置详解:为快照测试注册自定义序列化器

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

snapshotSerializers是 Vitest 测试配置中的一个数组型选项,用于隐式注册自定义快照序列化器(snapshot serializer),从而改变快照内容的序列化输出。当团队需要让生成的快照更易读、隐藏无关细节或按领域规则格式化对象时,这一配置是比在每个测试文件里手动调用expect.addSnapshotSerializer更干净、可复用的方案。读完本文,你将掌握该配置的类型约束、底层加载与校验机制,并能基于仓库源码写出可落地的自定义序列化器。

配置概览

  • 类型(Type):string[]
  • 默认值(Default):[]

该选项的值是一个模块路径数组,每个路径指向一个快照序列化器模块。Vitest 会在测试运行前加载这些模块,并把它们注册到快照渲染管线中,无需在每个测试文件中手动引入。更详细的自定义序列化器指南见 Custom Serializer。

类型定义位于 packages/vitest/src/node/types/config.ts#L619:

snapshotSerializers?: string[]

在 CLI 层,该选项也作为可配置项暴露(见 packages/vitest/src/node/cli/cli-config.ts#L982 的snapshotSerializers: null占位),意味着它与其他test配置一样会被完整的配置解析链路处理。

为什么需要自定义序列化器

Vitest 的快照渲染由@vitest/pretty-format驱动,默认内置了针对常见类型的序列化器:JavaScript 内置类型、HTML 元素、ImmutableJS 数据结构以及 React 元素。

默认输出对大多数场景足够,但当你遇到以下情况时,默认序列化器就显得力不从心:

  • 业务对象中包含大量与断言无关的元数据(如createdAt、内部 id),快照噪音大、每次变更都产生无谓 diff;
  • 需要以特定领域格式输出(如key=value行、XML、SQL);
  • 希望在快照中隐藏敏感信息或超长内容。

此时可以注入自己的序列化逻辑。Vitest 提供两条途径:

  1. 显式注册:在测试文件中调用expect.addSnapshotSerializer
  2. 隐式注册:通过snapshotSerializers配置项在全局加载——这正是本文的主题,适合跨文件、跨项目复用的序列化器。

序列化器接口:serialize/test 与 print/test

一个序列化器模块必须导出默认对象,并满足SnapshotSerializer类型。从 packages/pretty-format/src/types.ts#L206-L235 可以看到,pretty-format 同时支持新旧两种插件形态:

// 新式插件(推荐) export interface NewPlugin { serialize: ( val: any, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer, ) => string test: Test // (arg0: any) => boolean } // 旧式插件 export interface OldPlugin { print: ( val: unknown, print: Print, indent: Indent, options: PluginOptions, colors: Colors, ) => string test: Test }

两者都包含一个test谓词函数:它接收待序列化的值,返回true表示该序列化器接管此值的渲染;serialize(或旧式print)则执行真正的渲染逻辑。printer参数是一个回退函数——当你不想完全接管某个值时,可以用它调用既有的插件链继续渲染子值。

Vitest 在加载配置指定的序列化器时,会严格校验模块形态。见 packages/vitest/src/runtime/setup-common.ts#L64-L95:

export async function loadSnapshotSerializers( config: SerializedConfig, moduleRunner: PublicModuleRunner, ): Promise<void> { const files = config.snapshotSerializers const snapshotSerializers = await Promise.all( files.map(async (file) => { const mo = await moduleRunner.import(file) if (!mo || typeof mo.default !== 'object' || mo.default === null) { throw new Error( `invalid snapshot serializer file ${file}. Must export a default object`, ) } const config = mo.default if ( typeof config.test !== 'function' || (typeof config.serialize !== 'function' && typeof config.print !== 'function') ) { throw new TypeError( `invalid snapshot serializer in ${file}. Must have a 'test' method along with either a 'serialize' or 'print' method.`, ) } return config as SnapshotSerializer }), ) snapshotSerializers.forEach(serializer => addSerializer(serializer)) }

这意味着:

  • 模块必须默认导出(default export)一个对象,否则报invalid snapshot serializer file ... Must export a default object
  • 对象必须包含test函数,并且serializeprint至少具备其一,否则抛出TypeError
  • 所有序列化器会通过addSerializer统一注册进渲染管线,与expect.addSnapshotSerializer注册的序列化器合并生效(该 API 的类型声明见 packages/vitest/src/types/global.ts#L48)。

配置解析细节:路径解析与监听触发

snapshotSerializers在配置解析阶段会被规范化。见 packages/vitest/src/node/config/resolveConfig.ts#L668-L672:

resolved.snapshotSerializers ??= [] resolved.snapshotSerializers = resolved.snapshotSerializers.map(file => resolvePath(file, resolved.root), ) resolved.forceRerunTriggers.push(...resolved.snapshotSerializers)

两处值得注意的实现细节:

  1. 相对路径基于root解析:配置中写的路径会先被resolvePath(file, resolved.root)转换为绝对路径,因此配置值通常写成相对于项目根目录的形式;
  2. 自动加入强制重跑触发列表:解析完成后,这些序列化器文件路径会被追加到forceRerunTriggers。这意味着在 watch 模式下,当你修改序列化器文件本身时,Vitest 会自动重跑相关测试——修改序列化逻辑不再需要手动重启。

完整实战:把业务对象渲染成自定义格式

下面基于 docs/guide/snapshot.md#custom-serializer 的官方示例,完整演示从「定义序列化器」到「配置生效」的全流程。

第 1 步:编写序列化器模块

新建path/to/custom-serializer.ts,默认导出一个满足SnapshotSerializer的对象:

import { SnapshotSerializer } from 'vitest' export default { serialize(val, config, indentation, depth, refs, printer) { // `printer` 是一个函数,可借助既有插件渲染子值 return `Pretty foo: ${printer( val.foo, config, indentation, depth, refs, )}` }, test(val) { return val && Object.prototype.hasOwnProperty.call(val, 'foo') }, } satisfies SnapshotSerializer

逻辑说明:

  • test:只要传入值自身携带foo属性,就由本序列化器接管;
  • serialize:输出Pretty foo: <子值序列化结果>,子值通过printer递归交给默认渲染管线,从而保留对象内部的常规格式化。

第 2 步:在配置中注册

vitest.config.ts中启用:

import { defineConfig } from 'vitest/config' export default defineConfig({ test: { snapshotSerializers: ['path/to/custom-serializer.ts'], }, })

第 3 步:编写测试并观察快照

test('foo snapshot test', () => { const bar = { foo: { x: 1, y: 2, }, } expect(bar).toMatchSnapshot() })

由于bar含有foo属性,序列化被自定义逻辑接管,生成的快照如下:

Pretty foo: Object { "x": 1, "y": 2, }

而如果没有注册该序列化器,默认输出会是完整的Object { "foo": Object { "x": 1, "y": 2 } }结构。

expect.addSnapshotSerializer的关系

snapshotSerializersexpect.addSnapshotSerializer注册的是同一种插件对象,二者差异仅在于作用域与生命周期

方式作用域典型场景
expect.addSnapshotSerializer当前测试文件(需在测试内调用)单文件内的一次性自定义
snapshotSerializers配置整个项目所有测试团队级、跨文件复用的统一序列化规则

实际运行中,两者注册的序列化器会进入同一条 pretty-format 插件链,test谓词决定匹配优先级,因此配置全局注册后,测试文件里仍可叠加局部序列化器。

加载时序与运行原理

结合源码可以还原snapshotSerializers的完整生命周期:

  1. 配置解析(Node 侧):resolveConfig.ts将相对路径基于root解析为绝对路径,并追加进forceRerunTriggers
  2. 配置序列化(Node → Worker):packages/vitest/src/node/config/serializeConfig.ts#L38 将snapshotSerializers随序列化配置下发到执行端;
  3. 运行时加载:Worker 侧的loadSnapshotSerializers通过moduleRunner.import(file)逐个导入模块,校验默认导出形态后调用addSerializer注册;
  4. 快照渲染:测试执行toMatchSnapshot等断言时,pretty-format 遍历插件链,命中test谓词的序列化器接管渲染。

由此可见,序列化器模块运行在测试执行环境中,可以正常使用依赖与工具函数;而校验逻辑保证了配置错误会在测试启动阶段即被清晰报出,而不是等到快照比对时才产生诡异输出。

与 snapshotFormat 及文件快照的配合

  • 全局格式:如需调整序列化之外的整体渲染行为(如还原 Jest 的printBasicPrototype、缩进、引号风格),应配合snapshotFormat配置使用;snapshotSerializers专注「特定类型的自定义渲染」,两者正交、可叠加。
  • 文件快照:若只是希望快照以原始文件形式存在(不经过字符串转义、可自由选择扩展名),可考虑toMatchFileSnapshot,此时序列化器同样对其生效,因为它复用的是同一套 pretty-format 渲染管线。

常见报错与排查

结合源码校验逻辑,以下报错通常对应这些原因:

报错信息原因
invalid snapshot serializer file ${file}. Must export a default object模块没有默认导出,或默认导出不是对象(如用了export const而非export default
Must have a 'test' method along with either a 'serialize' or 'print' method默认导出对象缺少test函数,或serialize/print均为缺失
序列化器不生效路径未基于root解析正确(可用绝对路径验证);或test谓词未命中目标值类型
watch 模式修改序列化器不重跑一般不会发生:解析阶段已自动加入forceRerunTriggers,若仍异常请检查配置文件是否被 Vitest 监听覆盖范围排除

小结

snapshotSerializers是 Vitest 全局化自定义快照输出的入口:类型为string[]、默认[],每个元素指向一个默认导出{ test, serialize }(或{ test, print })对象的模块。它在配置解析阶段完成路径解析并自动纳入 watch 重跑触发,在运行时经loadSnapshotSerializers校验后注册进 pretty-format 插件链,与expect.addSnapshotSerializer共享同一渲染管线。对于需要统一、可复用快照格式的团队,这是比逐文件手动注册更符合工程化的方案。

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询