[Jest] 测试快照

在对组件进行测试的时候,往往需要从两个方面进行测试:

  • 交互:确保组件在进行交互时功能正常
  • 渲染:确保组件渲染输出正确(比如不会多一个或者少一个 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;

这个组件非常的简单,就是一个 todolist 的组件,内部有默认的项目,也可以新增项目。

接下来编写我们的测试代码,代码如下:

import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  const { baseElement } = render(<App />);
  expect(baseElement).toMatchSnapshot();
});

首先从 render 方法中解构出 baseElement(注意 render 方法来源于 testing library)

接下来调用了 toMatchSnapshot(这个方法是 jest 提供的方法)来生成快照。

image-20230511092759962

通过执行结果也可以看到,生成了一张快照,并且在我们的项目目录中(和你的测试文件是同级的),生成了一个名为 _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 节点),那么就说明这一次渲染和之前的渲染不一致的,测试不通过。

如下图所示:

image-20230511093235552

测试快照虽然很简单,但是有一些注意点:

  • 快照本身并不验证渲染逻辑是否正确,它只是防止意外更改,所以当测试快照不通过的时候,我们就需要检查一下所需的元素、样式是否发生了我们不期望的改变
  • 快照失败的时候,如果确定渲染逻辑没有问题,确确实实是结构需要发生更改,那么我们可以更新快照。可以通过 jest --updateSnapshot 这个命令进行更新。

避免大快照

在上面的示例中,我们的组件比较简单,因此我们针对整个组件做了快照,但是在真实的项目中,往往业务组件会比较复杂,一个大组件里面会嵌套很多的小组件,这个时候如果你直接对整个大组件进行快照,那么就会导致你的快照文件无比的巨大,因为这里快照会将嵌套的组件的 DOM 结构也记录下来。

例如:

import TestUI from "./components/TestUI"
import Items from "./components/Items"

function App() {
  return (
    <div className="App">
      <Items/>
      <TestUI/>
    </div>
  );
}

export default App;

function TestUI(){
    return (
        <ul data-testid="list">
            <li>张三</li>
            <li>李四</li>
            <li>王武</li>
        </ul>
    );
}

export default TestUI;
import { useState } from "react";
function Items() {
  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>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <button onClick={addItem}>添加</button>
      <ul>{lis}</ul>
    </div>
  );
}

export default Items;

那么现在针对 App 组件生成快照的时候,就会导致快照文件比较大,因为会连同 TestUI 以及 Items 组件的 DOM 结构一起生成。

这种请求,我们就可以指定只生成某一个部分的快照,这种快照我们称之为小快照。

代码如下:

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const content = screen.getByTestId('list');
  expect(content).toMatchSnapshot();
});

上面的代码,只会针对 TestUI 组件生成快照。

扩展场景

很多人喜欢把快照测试等同于组件的 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();
  })
});

总结

这一小节我们学会了 快照测试。快照测试的思想很简单:

先执行一次测试,把输出结果记录到 .snap 文件,以后每次测试都会把输出结果和 .snap 文件做对比。快照失败有两种可能:

  • 业务代码变更后导致输出结果和以前记录的 .snap 不一致,说明业务代码有问题,要排查 Bug
  • 业务代码有更新导致输出结果和以前记录的 .snap 不一致,新增功能改变了原有的 DOM 结构,要用 npx jest --updateSnapshot 更新当前快照。

不过现实中这两种失败情况并不好区分,更多的情况是你既在重构又要加新需求,这就是为什么快照测试会出现 “假错误”。而如果开发者还滥用快照测试,并生成很多大快照, 那么最终的结果是没有人再相信快照测试。一遇到快照测试不通过,都不愿意探究失败的原因,而是选择更新快照来 “糊弄一下”。

要避免这样的情况,需要做好两点:

  • 生成小快照。只取重要的部分来生成快照,必须保证快照是能让你看懂的
  • 合理使用快照。快照测试不是只为组件测试服务,同样组件测试也不一定要包含快照测试。快照能存放一切可序列化的内容。
posted @   Zhentiw  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2024-01-29 [Typescript 5] infer Constraints
2024-01-29 [Typescript] Infer first argument of a function
2024-01-29 [Typescript] Importing non-TS things
2024-01-29 [Typescript] Native ES module support
2023-01-29 [Docker] Storing Container Data in Azure Blob Storage
2023-01-29 [Typescript] Understanding Generics at Different Levels of Functions
2019-01-29 [Algorithom] Shuffle an array
点击右上角即可分享
微信分享提示