每当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。

典型的做法是在渲染了UI组件之后,保存一个快照文件, 检测他是否与保存在单元测试旁的快照文件相匹配。 若两个快照不匹配,测试将失败:有可能做了意外的更改,或者UI组件已经更新到了新版本。

Jest快照测试

测试React组件可以采用类似的方法。 你只需要测试对应的React树的序列号值即可,而不需要渲染整个React程序。 example test for a Link component官网例子如下 :

  1. import React from 'react';
  2. import renderer from 'react-test-renderer';
  3. import Link from '../Link.react';
  4. it('renders correctly', () => {
  5. const tree = renderer
  6. .create(<Link page="http://www.facebook.com">Facebook</Link>)
  7. .toJSON();
  8. expect(tree).toMatchSnapshot();
  9. });

当第一次运行这个单元测试,Jest创建了一个 snapshot file快照,类似这样:

  1. exports[`renders correctly 1`] = `
  2. <a
  3. className="normal"
  4. href="http://www.facebook.com"
  5. onMouseEnter={[Function]}
  6. onMouseLeave={[Function]}
  7. >
  8. Facebook
  9. </a>
  10. `;

快照文件应该和项目代码一起提交并做代码评审。 Jest使用 pretty-format 使快照代码在代码评审时更具可读。 在随后的单元测试例子中,Jest会对比上一个快照和渲染输出。 如果它们相匹配,则测试通过。 若未能匹配,要么是单元测试发现了你代码中的Bug,要么是代码实现已经变了,需要更新测试快照文件。 <Link>

小提示:快照测试只会测试数据渲染本身,在我们的例子中是测试向 <Link />组件 传递了 page 属性的Prop后的渲染。 这意味着如果使用<Link />的组件时未传递Prop,依然能通过测试,因为快照测试无法知道组件内部的逻辑。译者:只有两次快照的对比才能显示出UI变化的差异。 并且,对同一个组件传递不同Prop不会影响到第一个用例,他们相互独立,无法混淆信息。

More information on how snapshot testing works and why we built it can be found on the release blog post. 我们推荐你阅读 另外一些博客来知晓如何更好的使用快照。 我们也推荐您观看 egghead video 这个视频来学习如何使用快照。

更新快照

在代码引入错误后,很容易就通过快照发现为何单元测试失败了。 发生这种情况时,需要解决以使你的快照测试再次通过。 现在,让我们讨论一个故意使快照测试失败的案例。

如果我们有意的更改上述示例中的page属性,就会出现这种情况。

  1. // Updated test case with a Link to a different address
  2. it('renders correctly', () => {
  3. const tree = renderer
  4. .create(<Link page="http://www.instagram.com">Instagram</Link>)
  5. .toJSON();
  6. expect(tree).toMatchSnapshot();
  7. });

这个例子中,Jest将会打印出:

快照测试 - 图1

由于我们更改了测试用例中传入的page地址,我们也希望快照能因此改变。 快照测试失败了,原因是我们更新后的组件快照和之前形成的快照不一致。

要解决这个问题,我们需要更新我们已存储的快件文件。 你可以运行Jest 加一个标识符来重新生成快照文件。

  1. jest --updateSnapshot

运行上面的命令行,并接受更改。 你可以用单个字符完成同样的事情 ,如 jest -u。 这将为所有失败的快照测试重新生成快照文件。 如果我们无意间产生了Bug导致快照测试失败,应该先修复这些Bug,再生成快照文件,以避免用快照录制了错误的行为。

如果你想限制只重新生成一部分的快照文件,你可以使用--testNamePattern 来正则匹配想要生成的快照名字。

你可以通过克隆 snapshot example,修改其中的 Link 组件来尝试了解这个功能。

交互式快照模式

失败的快照也可以在监听模式下交互式的更新。

快照测试 - 图2

进入交互式快照模式后,Jest会让你浏览失败的快照,并让你可以看到失败用例的输出日志。

这样,你就可以选择更新快照,或者跳至下一步。

快照测试 - 图3

完成后,Jest在返回监听模式前输出一份总结。

快照测试 - 图4

内联快照

内联快照和普通快照(.snap 文件)表现一致,只是会将快照值自动写会源代码中。 这意味着你可以从自动生成的快照中受益,并且不用切换到额外生成的快照文件中保证值的正确性。

Inline snapshots are powered by Prettier. Inline snapshots are powered by Prettier. To use inline snapshots you must have prettier installed in your project. Your Prettier configuration will be respected when writing to test files. Your Prettier configuration will be respected when writing to test files.

If you have prettier installed in a location where Jest can’t find it, you can tell Jest how to find it using the "prettierPath" configuration property.

Example:

首先,你写一个测试,并调用 .toMatchInlineSnapshot() ,不需要传递参数。

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot();
  6. });

下次运行Jest时,tree 会被重新评估,此次的快照会被当作一个参数传递到 toMatchInlineSnapshot:

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot(`
  6. <a
  7. className="normal"
  8. href="https://prettier.io"
  9. onMouseEnter={[Function]}
  10. onMouseLeave={[Function]}
  11. >
  12. Prettier
  13. </a>
  14. `);
  15. });

这就是所有的了。 你甚至可以在--watch 模式中使用--updateSnapshot-u 来更新快照。

属性匹配器

项目中常常会有不定值字段生成(例如IDs和Dates)。 如果你试图对这些对象进行快照测试,每个测试都会失败。

  1. it('will fail every time', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot();
  8. });
  9. // Snapshot
  10. exports[`will fail every time 1`] = `
  11. Object {
  12. "createdAt": 2018-05-19T23:36:09.816Z,
  13. "id": 3,
  14. "name": "LeBron James",
  15. }
  16. `;

针对这些情况,Jest允许为任何属性提供匹配器(非对称匹配器)。 在快照写入或者测试前只检查这些匹配器是否通过,而不是具体的值。

  1. it('will check the matchers and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot({
  8. createdAt: expect.any(Date),
  9. id: expect.any(Number),
  10. });
  11. });
  12. // Snapshot
  13. exports[`will check the matchers and pass 1`] = `
  14. Object {
  15. "createdAt": Any<Date>,
  16. "id": Any<Number>,
  17. "name": "LeBron James",
  18. }
  19. `;

任何其他不是匹配器的值都会被准确检查并保存到快照文件中。

  1. it('will check the values and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. name: 'Bond... James Bond',
  5. };
  6. expect(user).toMatchSnapshot({
  7. createdAt: expect.any(Date),
  8. name: 'Bond... it('will check the values and pass', () => {
  9. const user = {
  10. createdAt: new Date(),
  11. name: 'Bond... James Bond',
  12. };
  13. expect(user).toMatchSnapshot({
  14. createdAt: expect.any(Date),
  15. name: 'Bond... James Bond',
  16. });
  17. });
  18. // Snapshot
  19. exports[`will check the values and pass 1`] = `
  20. Object {
  21. "createdAt": Any<Date>,
  22. "name": 'Bond... James Bond',
  23. }
  24. `; James Bond',
  25. }
  26. `;

最佳实践

快找是一个用于检测API响应、UI、日志和错误信息的好工具。 和其他的测试策略一样,逆序遵循一些准则来有效的利用他们,以完成最佳实践。

1. 视快照如代码

像提交并审核常规代码一样去提交和审核快照。 这就意味着要像你在项目中对待其他测试和代码一样对待快照。

确保你的快照更易读,方法是保持聚焦,剪短,并使用工具遵循规范以保持代码风格。

如前所述,Jest使用 pretty-format来保证快照的可读性,但你会发现引入额外的工具也是很有用的。例如 使用eslint-plugin-jestno-large-snapshotssnapshot-diff 的快照比较来保证更简短和更聚焦的断言。

目标是能让我们轻松的在代码PR中审核快照,改掉测试快照失败时就重新生成快照的坏习惯,正确的做法是查找错误的根源

2. 测试应是确定性的

你所有的测试都应该是确定的。 任何时候测试未改变的组件都应该产生相同的结果。 你需要确保你的快照测试与平台和其他不相干数据无关。

举个例子,如果你的Clock 组件使用 Date.now(), 每次运行测试快照都会不同。 这种情况,我们可以使用 mock the Date.now() method 重写Date.now, 去返回确定值。

  1. Date.now = jest.fn(() => 1482363367071);

现在,每次快照运行时,Date.now() 都会返回确定的值。 这样每次快照在不同时间运行就依然能产生相同的快照了。

3. 使用合理的快照描述

总是尽量好好描述你的测试和快照。 最好的描述名称是写出期望的返回内容。 这能让审查人员在审查过程中更容易验证快照,也能让任何人在更改代码之前知道这个快照已过时。

例如,对比以下例子:

  1. exports[`<UserName /> should handle some test case`] = `null`;
  2. exports[`<UserName /> should handle some other test case`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

到:

  1. exports[`<UserName /> should render null`] = `null`;
  2. exports[`<UserName /> should render Alan Turing`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

因为后者更好的描述了期望的输出,很容易就发现快照为什么未通过。

  1. exports[`<UserName /> should render null`] = `
  2. <div>
  3. Alan Turing
  4. </div>
  5. `;
  6. exports[`<UserName /> should render Alan Turing`] = `null`;

常见问题

持续集成系统(CI) 中也会生成快照文件吗?

不。截止到Jest 20 版本,如果没有明确传入 --updateSnapshot,快照是不会自动写入CI系统的。 预计所有快照都是在CI上运行的代码的一部分,并且由于新快照会自动通过,因此它们不应通过在CI系统上运行的测试。 建议始终提交所有快照并将其保留在版本控制中。

Should snapshot files be committed?

Yes, all snapshot files should be committed alongside the modules they are covering and their tests. They should be considered part of a test, similar to the value of any other assertion in Jest. In fact, snapshots represent the state of the source modules at any given point in time. In this way, when the source modules are modified, Jest can tell what changed from the previous version. It can also provide a lot of additional context during code review in which reviewers can study your changes better. They should be considered part of a test, similar to the value of any other assertion in Jest. In fact, snapshots represent the state of the source modules at any given point in time. In this way, when the source modules are modified, Jest can tell what changed from the previous version. It can also provide a lot of additional context during code review in which reviewers can study your changes better.

Does snapshot testing only work with React components?

React and React Native components are a good use case for snapshot testing. However, snapshots can capture any serializable value and should be used anytime the goal is testing whether the output is correct. The Jest repository contains many examples of testing the output of Jest itself, the output of Jest’s assertion library as well as log messages from various parts of the Jest codebase. See an example of snapshotting CLI output in the Jest repo.

What’s the difference between snapshot testing and visual regression testing?

Snapshot testing and visual regression testing are two distinct ways of testing UIs, and they serve different purposes. Visual regression testing tools take screenshots of web pages and compare the resulting images pixel by pixel. With Snapshot testing values are serialized, stored within text files, and compared using a diff algorithm. There are different trade-offs to consider and we listed the reasons why snapshot testing was built in the Jest blog. Visual regression testing tools take screenshots of web pages and compare the resulting images pixel by pixel. With Snapshot testing values are serialized, stored within text files, and compared using a diff algorithm. There are different trade-offs to consider and we listed the reasons why snapshot testing was built in the Jest blog.

Does snapshot testing replace unit testing?

Snapshot testing is only one of more than 20 assertions that ship with Jest. The aim of snapshot testing is not to replace existing unit tests, but to provide additional value and make testing painless. Snapshot testing is only one of more than 20 assertions that ship with Jest. The aim of snapshot testing is not to replace existing unit tests, but to provide additional value and make testing painless. In some scenarios, snapshot testing can potentially remove the need for unit testing for a particular set of functionalities (e.g. React components), but they can work together as well.

What is the performance of snapshot testing regarding speed and size of the generated files?

Jest has been rewritten with performance in mind, and snapshot testing is not an exception. Since snapshots are stored within text files, this way of testing is fast and reliable. Jest generates a new file for each test file that invokes the toMatchSnapshot matcher. The size of the snapshots is pretty small: For reference, the size of all snapshot files in the Jest codebase itself is less than 300 KB.

How do I resolve conflicts within snapshot files?

Snapshot files must always represent the current state of the modules they are covering. Snapshot files must always represent the current state of the modules they are covering. Therefore, if you are merging two branches and encounter a conflict in the snapshot files, you can either resolve the conflict manually or update the snapshot file by running Jest and inspecting the result.

Is it possible to apply test-driven development principles with snapshot testing?

Although it is possible to write snapshot files manually, that is usually not approachable. Although it is possible to write snapshot files manually, that is usually not approachable. Snapshots help to figure out whether the output of the modules covered by tests is changed, rather than giving guidance to design the code in the first place.

Does code coverage work with snapshot testing?

Yes, as well as with any other test.