当Jest在2016年宣布快照测试时,我非常兴奋。它感觉是测试React组件的一个完美解决方案。从那时起,我写的快照测试越来越少了。在这篇文章中,我将尝试解释原因。
我将主要谈论测试React组件,因为这是我使用或看到快照最多的地方,但同样也可以适用于测试其他东西。
为什么快照会有问题
容易提交带有bug的快照
当你写expect(mycomponent).toMatchSnapshot() ,Jest会创建一个快照文件,像mytest.spec.js.snap ,里面有你测试文件的所有快照。除非你真的想,否则你不会看到这个文件。
这是一个问题:你还不知道你的代码是否正确,除非你仔细阅读快照文件的每一行,否则你可能会提交带有错误的代码和确保错误仍然存在的快照。
失败是很难理解的
通过查看快照的失败差异,往往很难看出什么变化:快照失败是因为你的预期修改还是因为你引入了一个bug?

我们往往不假思索地更新快照
在一个大项目中,一个简单的变化可能会导致代码库中不同部分的几十个快照失效。了解每一个故障都需要大量的时间,所以往往开发人员只是更新快照而根本不看它们,特别是如果这不是他们的代码。
与低层模块的耦合
快照--尤其是当你不使用浅层渲染时--常常会因为低层组件的变化或第三方软件包的更新而发生故障。
一个Button组件的小变化可能会导致数百个快照的失败,即使一切正常。Button组件是一个实现细节,如果一个按钮仍然表现得像一个按钮,测试就应该通过。
测试意图难以理解
像 "应该呈现一个组件的默认状态 "这样的快照测试很常见,但并没有真正解释他们到底在测试什么,如果有的话。
这样的快照在标记的任何变化中都会失效,即使行为保持不变。
一种错误的安全感
快照并不能验证组件的外观是否与以前一样,也不能验证组件的行为是否正确。尽管这样,我们很容易相信它们是正确的,并且做一些实际上没有测试的测试,但却给你提供了高的测试覆盖率。
快照只验证了组件的渲染(指的是它的HTML,而不是它在浏览器中的样子)是否相同,这很少是重要的知识。
提示视觉快照是一种验证你的组件看起来和以前一样的方法。有许多工具,如Percy、Chromatic或Shutter,可以做到这一点。
用什么来代替快照
让我们看一下一个典型的测试(我自己也写过类似的测试):
test('show a success message after submission', () => {
const { getByText } = render(<Form />);
expect(wrapper).toMatchSnapshot();
fireEvent.click(getByText('Send an owl'));
expect(wrapper).toMatchSnapshot();
});
我们在这里有两个快照:这两个快照在任何标记变化时都会中断。两者的意图并不明确。第一个可能已经被其他测试用例所覆盖,所以我们可以把它删除。在第二个例子中,我们可能想知道成功信息是否被显示出来,而不是一个表单,这在代码中根本不清楚。我们正好可以测试这一点:
test('show success message after submission', () => {
const { getByText, getByTestId } = render(<Form />);
fireEvent.click(getByText('Send an owl'));
// The form has gone
expect(getByTestId('message-form')).not.toBeDefined();
// The success message is here
expect(
getByTestId('message-alert').textContent
).toMatchInlineSnapshot(`Owl has been sent!`);
});
现在的测试意图很明确:我们点击提交按钮,看到的不是表单,而是一条成功信息。这个测试不会因为原始组件或第三方依赖关系的更新而失败。这个测试也不会因为不影响行为的布局变化而失败。
何时使用快照
我认为快照在某些情况下仍然有用。
- 意图明确的非常短的输出,如类名或错误信息:
expect(className).toMatchInlineSnapshot(
`Box Text Text--align-center Text--variation-warning`
);
- 当你真的想验证输出是否相同的时候:
exports[`macros basic usage: basic usage 1`] = `
"
import wcImport from \\"../macro\\";
const asyncModule = wcImport(\\"./MyComponent\\");
↓ ↓ ↓ ↓ ↓ ↓
const asyncModule = import(/* webpackChunkName: MyComponent */ \\"./MyComponent\\");
"
`;
快照用量
- 使用内联快照,使快照在测试文件中可见,并帮助你保持小的快照:
expect(error).toMatchInlineSnapshot(`Error: Out of cheese!`);
- 使用快照属性匹配器来避免存储生成的值,如日期或ID:
expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number)
});
- 使用描述性的快照名称,以便更容易发现快照的不正确之处。
例如,比较:
exports[`<Animal /> should handle some test case`] = `null`;
exports[
`<Animal /> should handle some other test case`
] = `<strong>dog</strong>`;
到:
exports[`<Animal /> should render null`] = `null`;
exports[
`<Animal /> should render an animal name`
] = `<strong>dog</strong>`;
有了后者,快照出错时就很清楚了,就像这样:
exports[`<Animal /> should render null`] = `<strong>dog</strong>`;
exports[`<Animal /> should render an animal name`] = `null`;
快照注意事项
- 避免快照文件路径:在其他平台上会因为不同的分隔符(
\vs/)而失败。 - 避免快照日期和时间:即使日期只取决于输入,在另一个时区也可能不同,你的测试将失败。
总结
在一些罕见的情况下,快照是有用的,但通常它们会给你一种错误的安全感,同时使代码库更难改变。