前言
此前的文章我们主要学习了关于react组件单测的各种情况,已经可以基本覆盖组件单测的基本场景,今天我们补充一个新的概念,快照测试。
快照测试
可以回顾下我们往期的文章,之前已经提过了单测的作用以及在研发流程上带来的一些收益,这里我们举个例子,我们在实际项目中会出现某些模块写的不好然后进行代码上的重构,改造完成之后我们需要对我们重构或者改造后的功能进行冒烟测试,而快照测试也是单测中一种保证冒烟的有效手段。如果我们想要确保我们改造后UI上不会有以外的变化,快照测试就是一款非常有用的工具。典型的做法就是在渲染了UI组件之后,保存一个快照文件,检测这个快照文件是否与保存在单测旁的快照文件想匹配,若两个快照文件不匹配,测试即视为失败,有可能做了以外的更改,或者UI组件已经更新到了新版本。
写一个新的组件
我们在components目录下新建一个todo-content
的文件夹,用于写我们用于做快照测试的组件
cd src/components
mkdir todo-header
cd todo-header
touch index.tsx
组件内容
import { Card, Input } from "antd";
const { TextArea } = Input;
interface TodoContentProps {
title: string;
content: string;
}
export default function TodoContent({ title, content }: TodoContentProps) {
return (
<Card title={title} style={{ width: 300 }}>
<TextArea rows={4} value={content} />
</Card>
);
}
在App.tsx中调用
<TodoContent title="这是标题" content="这是一个很长很长的内容呀..." />
启动项目看下效果pnpm start
快照初步
同之前的例子一样,我们先针对这个TodoContent组件编写单测
在src/components/__tests__
目录下创建关于这个组件的单测文件todo-content.test.tsx
import { render } from "@testing-library/react";
import TodoContent from "../todo-content";
const TITLE = "这是一个标题";
const CONTENT = "这是一个内容";
describe("测试TodoContent", () => {
it("正确渲染TodoContent组件", () => {
const { queryByText } = render(
<TodoContent title={TITLE} content={CONTENT} />
);
const titleElement = queryByText(TITLE);
const contentElement = queryByText(CONTENT);
expect(titleElement).not.toBeNull();
expect(titleElement).toBeInTheDocument();
expect(contentElement).not.toBeNull();
expect(contentElement).toBeInTheDocument();
});
it("正确匹配快照", () => {
const { asFragment } = render(
<TodoContent title={TITLE} content={CONTENT} />
);
expect(asFragment()).toMatchSnapshot();
});
});
运行这个单测
pnpm test src/components/__tests__/todo-content.test.tsx
看下效果
这时候我们留意一下我们这个单测文件的同级目录,这时候已经出现了__snapshots__这个目录,还有生成了一个todo-content.test.tsx.snap
看下快照的内容
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`测试TodoContent 正确匹配快照 1`] = `
<DocumentFragment>
<div
class="ant-card ant-card-bordered"
style="width: 300px;"
>
<div
class="ant-card-head"
>
<div
class="ant-card-head-wrapper"
>
<div
class="ant-card-head-title"
>
这是一个标题
</div>
</div>
</div>
<div
class="ant-card-body"
>
<textarea
class="ant-input"
rows="4"
>
这是一个内容
</textarea>
</div>
</div>
</DocumentFragment>
`;
可以看到根据我们render的组件然后创建了一个dom结构并且保存了下来,因为是第一次运行,这时候就不存在匹配快照的情况。
我们来走读下我们刚刚写的单测,我们在通过render
之后用到了asFragment
这个方法,通过这个方法我们生成类似于document的结构,然后就是断言部分,我们通过toMatchSnapshot
方法来匹配这个快照
我们改一下我们的组件的传参,然后重新运行下这个单测
const { asFragment } = render(
<TodoContent title={TITLE} content="我改了这个内容" />
);
重新运行这个单测看看结果
可以看到这时候原本已经可以运行通过的单测因为快照匹配失败而直接报错了
除了这种常见的组件入参之外,我们可以改一下组件的样式,把宽度改为了400,这时候看下这个快照能否匹配上
<Card title={title} style={{ width: 400 }}>
<TextArea rows={4} value={content} />
</Card>
可以看到这时候我们的快照匹配也是失败的,而且在报错信息中也明确的提示出来,我们的style方面匹配失败,这也是给了我们修改的方向
更新快照
从上面的例子我们可以看出,当我们写了单测之后,我们的snap是在第一次运行时候生成出来的,后面我们的一些改造实际上是没有问题的,但是由于快照没有更新,所以匹配就一直错误,这里就需要更新快照了
这里我们就需要用到jest的updateSnapshot
这个命令了,我们在package.json中增加一个更新快照的命令
"updateSnapshot": "react-scripts test --updateSnapshot"
运行这个命令
这时候我们看下我们的快照文件,我们改动的width样式已经正确更新到快照文件中
内联快照
内联快照在笔者实际应用这基本上没用过,比较常见都是上面这种情况,直接生成一个快照文件,内联快照和普通快照(
.snap
文件)表现一致,只是会将快照值自动写会源代码中。 这意味着你可以从自动生成的快照中受益,并且不用切换到额外生成的快照文件中保证值的正确性。 具体可以参考官方文档
定制快照内容
在上面的代码中,我们都是通过一个代码片段保存成为了一个快照,实际上我们可以定制这个快照保存的内容,除了上面的代码片段外,例如json,或者对象字符串等等都是可以成为我们的定制快照内容
我们单独写一个定制快照内容的单测
describe("测试定制对象的快照内容", () => {
it("正确匹配快照", () => {
const user = {
name: "eason",
age: 18,
};
expect(user).toMatchSnapshot({
name: "eason",
age: 18,
});
});
});
然后运行这个单测生成快照文件
可以看到我们通过定制化的形式生成了我们的快照文件,定制化的方式给了我们比较大的灵活性
一些经验
- 把快照文件也当做代码来看待,在生成出来的快照文件也应当作为提交的内容提交上去
- 使用合理的快照描述,好好描述你的测试和快照。最好的描述名称是写出期望的返回内容.这能我们在code review过程中更容易验证快照,也能让任何人在更改代码之前知道这个快照已过时。
传送门
前端单测学习(1)—— 单测入门之react单测项目初步
前端单测学习(2)—— react 组件单测初步
前端单测学习(3)—— react组件单测进阶
前端单测学习(4)—— react 组件方法&fireEvent
前端单测学习(5)—— 快照
前端单测学习(6)—— 定时器
前端单测学习(7)—— mock
前端单测学习(8)—— react hook
前端单测学习(9)—— 覆盖率报告
前端单测学习(10)—— 状态管理redux
前端单测学习(11)—— react hook 进阶
前端单测学习(12)—— 性能优化
前端单测学习(13)—— 自动化测试