测试快照
在对组件进行测试的时候,往往需要从两个方面进行测试:
- 交互:确保组件在进行交互时功能正常。
- 渲染:确保组件渲染输出正确(比如不会多一个或者少一个 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;
针对此组件,测试增加测试快照:
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { baseElement } = render(<App />);
// 生成测试快照
expect(baseElement).toMatchSnapshot();
});
通过执行结果可以看到,生成了一张快照,并且在项目目录中(与测试文件是同级的),生成了一个名为_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 { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
// 生成testid为list的快照
const content = screen.getByTestId('list');
expect(content).toMatchSnapshot();
});
扩展场景
很多开发者喜欢把快照测试等同于组件的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();
})
});