Jest 单元测试快速入门

wuchangjian2021-11-17 16:35:54编程学习

测试的类型

  • 单元测试
  • 集成测试
  • 端到端测试(E2E)

测试 React 组件需要解决两个关键问题

  • 怎么渲染待测试组件;
  • 怎么测试渲染出来的组件;

浅层渲染

对于组件渲染,Airbnb 作为重度使用 React 的先驱,早就提出了专门的解决方案:Enzyme

$ npm install enzyme enzyme-adapter-react-16

Enzyme 提供的一个重要功能便是组件的浅层渲染(Shallow Rendering)。它允许我们在运行测试时,只渲染父组件而不渲染其所有的子组件。浅层渲染十分快速,因此非常适合单元测试。

// src/App.test.js
import React from 'react';
import { shallow } from 'enzyme';

import App from './App';

describe('app component', () => {
  it('contains a header with the "Hello world!"', () => {
    const app = shallow(<App />);
    expect(app.containsMatchingElement(<h1>Hello world!</h1>)).toEqual(true);
  });
});

对于测试渲染出来的组件,Jest 自带的 Matcher 不太好用。实际上,社区还提供了更好的选择——专门为 Enzyme 定制的 Matcher 库:enzyme-matchers。这些 Matcher 使得编写断言语句更轻松、更具可读性。

$ npm install jest-enzyme

可以看到在第一个测试用例中,我们使用了 toContainReact 这个 Matcher,它的含义十分明显,一目了然;在后面的测试用例中,我们通过 todoList.find('li') 来获取 li 元素数组,并判断它的长度是否符合要求。

// src/TodoList.test.js
import React from 'react';
import { shallow } from 'enzyme';

import ToDoList from './ToDoList';

describe('ToDoList component', () => {
  describe('when provided with an empty array of tasks', () => {
    it('contains an empty <ul> element', () => {
      const toDoList = shallow(<ToDoList tasks={[]} />);
      expect(toDoList).toContainReact(<ul />);
    });

    it('does not contain any <li> elements', () => {
      const toDoList = shallow(<ToDoList tasks={[]} />);
      expect(toDoList.find('li').length).toEqual(0);
    });
  });

  describe('when provided with an array of tasks', () => {
    it('contains a matching number of <li> elements', () => {
      const tasks = ['Wash the dishes', 'Make the bed'];
      const toDoList = shallow(<ToDoList tasks={tasks} />);
      expect(toDoList.find('li').length).toEqual(tasks.length);
    });
  });
});

完全渲染

针对浅层渲染的局限性,Enzyme 提供了完全渲染函数 mount。修改 TodoList 测试文件,代码如下:

// src/TodoList.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';

import ToDoList from './ToDoList';

describe('ToDoList component', () => {
  // ...

    it('contains a matching number of <li> elements', () => {
      const toDoListInstance = mount(<ToDoList tasks={tasks} />);

      toDoListInstance.find('Task').forEach((taskInstance) => {
        const taskProps = taskInstance.props();
        const matchingTask = tasks.find((task) => task.id === taskProps.id);
        const listItem = taskInstance.first('li');
        expect(listItem.text()).toBe(matchingTask.name);
      });
    });
  });
});

由于 mount 函数会模拟实际的 DOM,渲染成本更高,因此运行测试会花费更多的时间。通常我们会在集成测试中使用 mount 函数,测试组件之间如何协同工作,而不仅仅是作为独立的单元。

Snapshot 测试

快照测试是 Jest 的一大招牌功能。所谓快照,可以简单地理解成是我们应用的一个 “代码截图” 。当我们运行快照测试时,Jest 将会渲染组件并创建其快照文件。这个快照文件包含渲染后组件的整个结构,并且应该与测试文件本身一起提交到代码库。当我们再次运行快照测试时,Jest 会将新的快照与旧的快照进行比较,如果两者不一致,测试就会失败,从而帮助我们确保用户界面不会发生意外改变。

在 TodoList 的测试代码中添加快照测试:

// src/TodoList.test.js
// ...

describe('ToDoList component', () => {
  // ...

  describe('when provided with an array of tasks', () => {
    // ...

    it('should render correctly', () => {
      const toDoListInstance = shallow(<ToDoList tasks={tasks} />);
      expect(toDoListInstance).toMatchSnapshot();
    });
  });
});

运行上面的代码后,就会产生 ToDoList.test.js.snap 文件,它的内容如下:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ToDoList component when provided with an array of tasks should render correctly 1`] = `
<ul>
  <Task
    id={0}
    key="0"
    name="Wash the dishes"
  />
  <Task
    id={1}
    key="1"
    name="Make the bed"
  />
</ul>
`;

如果我们要对 ToDoList 组件进行任何更改,快照测试就会失败,并且显示当前渲染结果与快照之间的精确差异。如果我们要更新所有失败的快照,可以使用 -u 标志(别名为 --updateSnapshot) 来运行 Jest。输入以下命令,一键更新所有快照:

$ npm test -- -u

参考

JavaScript 测试系列实战(一):使用 Jest 和 Enzyme 测试 React 组件

JavaScript 测试系列实战(二):深层渲染和快照测试

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。