[Jest] 测试快照
在对组件进行测试的时候,往往需要从两个方面进行测试:
- 交互:确保组件在进行交互时功能正常
- 渲染:确保组件渲染输出正确(比如不会多一个或者少一个 DOM 元素)
针对渲染方面的测试,我们就可以使用快照来进行测试。
所谓快照,就是给渲染出来的 DOM 元素拍一张“照片”(将最终渲染出来的 DOM 以字符串序列的方式记录下来)
快速上手
首先我们有如下的一个组件
import { useState } from "react";
function App() {
const [items, setItems] = useState(["苹果", "香蕉", "西瓜"]);
const [value, setValue] = useState("");
const lis = items.map((it, idx) => <li key={idx}>{it}</li>);
function addItem() {
if (items) {
const newItems = [...items];
newItems.push(value);
setItems(newItems);
setValue("");
}
}
return (
<div className="App">
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button onClick={addItem}>添加</button>
<ul>{lis}</ul>
</div>
);
}
export default App;
这个组件非常的简单,就是一个 todolist 的组件,内部有默认的项目,也可以新增项目。
接下来编写我们的测试代码,代码如下:
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { baseElement } = render(<App />);
expect(baseElement).toMatchSnapshot();
});
首先从 render 方法中解构出 baseElement(注意 render 方法来源于 testing library)
接下来调用了 toMatchSnapshot(这个方法是 jest 提供的方法)来生成快照。

通过执行结果也可以看到,生成了一张快照,并且在我们的项目目录中(和你的测试文件是同级的),生成了一个名为 _snapshots_ 的目录,里面就是一张测试快照,测试快照的本质就是渲染出来的 DOM 的结构的字符串序列。
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders learn react link 1`] = `
<body>
<div>
<div
class="App"
>
<input
type="text"
value=""
/>
<button>
添加
</button>
<ul>
<li>
苹果
</li>
<li>
香蕉
</li>
<li>
西瓜
</li>
</ul>
</div>
</div>
</body>
`;
之后在下一次测试的时候,针对这个组件测试,就会将组件渲染出来的 DOM 结构的序列和之前的快照进行一个比对,看是否一致,如果和之前的快照是一致的,那么测试就通过,如果不一致(这一次渲染新增了DOM节点或者少了DOM 节点),那么就说明这一次渲染和之前的渲染不一致的,测试不通过。
如下图所示:

测试快照虽然很简单,但是有一些注意点:
- 快照本身并不验证渲染逻辑是否正确,它只是防止意外更改,所以当测试快照不通过的时候,我们就需要检查一下所需的元素、样式是否发生了我们不期望的改变
- 快照失败的时候,如果确定渲染逻辑没有问题,确确实实是结构需要发生更改,那么我们可以更新快照。可以通过 jest --updateSnapshot 这个命令进行更新。
避免大快照
在上面的示例中,我们的组件比较简单,因此我们针对整个组件做了快照,但是在真实的项目中,往往业务组件会比较复杂,一个大组件里面会嵌套很多的小组件,这个时候如果你直接对整个大组件进行快照,那么就会导致你的快照文件无比的巨大,因为这里快照会将嵌套的组件的 DOM 结构也记录下来。
例如:
import TestUI from "./components/TestUI"
import Items from "./components/Items"
function App() {
return (
<div className="App">
<Items/>
<TestUI/>
</div>
);
}
export default App;
function TestUI(){
return (
<ul data-testid="list">
<li>张三</li>
<li>李四</li>
<li>王武</li>
</ul>
);
}
export default TestUI;
import { useState } from "react";
function Items() {
const [items, setItems] = useState(["苹果", "香蕉", "西瓜"]);
const [value, setValue] = useState("");
const lis = items.map((it, idx) => <li key={idx}>{it}</li>);
function addItem() {
if (items) {
const newItems = [...items];
newItems.push(value);
setItems(newItems);
setValue("");
}
}
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button onClick={addItem}>添加</button>
<ul>{lis}</ul>
</div>
);
}
export default Items;
那么现在针对 App 组件生成快照的时候,就会导致快照文件比较大,因为会连同 TestUI 以及 Items 组件的 DOM 结构一起生成。
这种请求,我们就可以指定只生成某一个部分的快照,这种快照我们称之为小快照。
代码如下:
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const content = screen.getByTestId('list');
expect(content).toMatchSnapshot();
});
上面的代码,只会针对 TestUI 组件生成快照。
扩展场景
很多人喜欢把快照测试等同于组件的 UI 测试,但是快照有些时候在其他的某一些场景下使用也非常方面。
举个例子:
// getUserById.ts
const getUserById = async (id: string) => {
return request.get('user', {
params: { id }
})
}
// getUserById.test.ts
describe('getUserById', () => {
it('可以获取 userId == 1 的用户', async () => {
const result = await getUserById('1')
expect(result).toEqual({
// 非常巨大的一个 JSON 返回...
})
})
});
比如在上面的示例中,http 请求返回的结果是比较大的,这个时候就会有一些冗余的代码,在写 expect 断言的时候就会比较麻烦。
这个时候你就可以使用快照:
// getUserById.ts
const getUserById = async (id: string) => {
return request.get('user', {
params: { id }
})
}
// getUserById.test.ts
describe('getUserById', () => {
it('可以获取 userId == 1 的用户', async () => {
const result = await getUserById('1')
expect(result).toMatchSnapshot();
})
});
总结
这一小节我们学会了 快照测试。快照测试的思想很简单:
先执行一次测试,把输出结果记录到 .snap 文件,以后每次测试都会把输出结果和 .snap 文件做对比。快照失败有两种可能:
- 业务代码变更后导致输出结果和以前记录的 .snap 不一致,说明业务代码有问题,要排查 Bug。
- 业务代码有更新导致输出结果和以前记录的 .snap 不一致,新增功能改变了原有的 DOM 结构,要用 npx jest --updateSnapshot 更新当前快照。
不过现实中这两种失败情况并不好区分,更多的情况是你既在重构又要加新需求,这就是为什么快照测试会出现 “假错误”。而如果开发者还滥用快照测试,并生成很多大快照, 那么最终的结果是没有人再相信快照测试。一遇到快照测试不通过,都不愿意探究失败的原因,而是选择更新快照来 “糊弄一下”。
要避免这样的情况,需要做好两点:
- 生成小快照。只取重要的部分来生成快照,必须保证快照是能让你看懂的
- 合理使用快照。快照测试不是只为组件测试服务,同样组件测试也不一定要包含快照测试。快照能存放一切可序列化的内容。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2024-01-29 [Typescript 5] infer Constraints
2024-01-29 [Typescript] Infer first argument of a function
2024-01-29 [Typescript] Importing non-TS things
2024-01-29 [Typescript] Native ES module support
2023-01-29 [Docker] Storing Container Data in Azure Blob Storage
2023-01-29 [Typescript] Understanding Generics at Different Levels of Functions
2019-01-29 [Algorithom] Shuffle an array