测试React组件中的快照测试指南

423 阅读5分钟

当Jest在2016年宣布快照测试时,我非常兴奋。它感觉是测试React组件的一个完美解决方案。从那时起,我写的快照测试越来越少了。在这篇文章中,我将尝试解释原因。

我将主要谈论测试React组件,因为这是我使用或看到快照最多的地方,但同样也可以适用于测试其他东西。

为什么快照会有问题

容易提交带有bug的快照

当你写expect(mycomponent).toMatchSnapshot() ,Jest会创建一个快照文件,像mytest.spec.js.snap ,里面有你测试文件的所有快照。除非你真的想,否则你不会看到这个文件。

这是一个问题:你还不知道你的代码是否正确,除非你仔细阅读快照文件的每一行,否则你可能会提交带有错误的代码和确保错误仍然存在的快照。

失败是很难理解的

通过查看快照的失败差异,往往很难看出什么变化:快照失败是因为你的预期修改还是因为你引入了一个bug?

我们往往不假思索地更新快照

在一个大项目中,一个简单的变化可能会导致代码库中不同部分的几十个快照失效。了解每一个故障都需要大量的时间,所以往往开发人员只是更新快照而根本不看它们,特别是如果这不是他们的代码。

与低层模块的耦合

快照--尤其是当你不使用浅层渲染时--常常会因为低层组件的变化或第三方软件包的更新而发生故障。

一个Button组件的小变化可能会导致数百个快照的失败,即使一切正常。Button组件是一个实现细节,如果一个按钮仍然表现得像一个按钮,测试就应该通过。

测试意图难以理解

像 "应该呈现一个组件的默认状态 "这样的快照测试很常见,但并没有真正解释他们到底在测试什么,如果有的话。

这样的快照在标记的任何变化中都会失效,即使行为保持不变。

一种错误的安全感

快照并不能验证组件的外观是否与以前一样,也不能验证组件的行为是否正确。尽管这样,我们很容易相信它们是正确的,并且做一些实际上没有测试的测试,但却给你提供了高的测试覆盖率。

快照只验证了组件的渲染(指的是它的HTML,而不是它在浏览器中的样子)是否相同,这很少是重要的知识。

提示视觉快照是一种验证你的组件看起来和以前一样的方法。有许多工具,如PercyChromaticShutter,可以做到这一点。

用什么来代替快照

让我们看一下一个典型的测试(我自己也写过类似的测试):

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!`);
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/ )而失败。
  • 避免快照日期和时间:即使日期只取决于输入,在另一个时区也可能不同,你的测试将失败。

总结

一些罕见的情况下,快照是有用的,但通常它们会给你一种错误的安全感,同时使代码库更难改变。