0%

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

@tuture-dev

查看代码

测试组件的 Props

在前一篇文章中,我们已经测试了传递 Props 给组件的情况。但实际上,我们可以直接测试 Props。让我们回到之前写的 ToDoList 组件,但是这次我们使用一个 Task 组件。

Task 组件的代码如下:

src/Task.js查看完整代码
import React from 'react';

const Task = (props) => {
return <li>{props.name}</li>;
};

export default Task;

修改后的 TodoList 组件代码如下:

src/TodoList.js查看完整代码
import React from 'react';
[tuture-add]import Task from './Task';

const ToDoList = (props) => {
return (
<ul>
[tuture-del] {props.tasks.map((taskName, index) => (
[tuture-del] <li key={index}>{taskName}</li>
[tuture-add] {props.tasks.map((task) => (
[tuture-add] <Task key={task.id} id={task.id} name={task.name} />
))}
</ul>
);
};

export default ToDoList;

然后我们来测试 ToDoList 组件是否能够渲染 Task 组件并传递正确的 Props:

src/TodoList.test.js查看完整代码
// ...

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

describe('when provided with an array of tasks', () => {
[tuture-del] it('contains a matching number of <li> elements', () => {
[tuture-del] const tasks = ['Wash the dishes', 'Make the bed'];
[tuture-del] const toDoList = shallow(<ToDoList tasks={tasks} />);
[tuture-del] expect(toDoList.find('li').length).toEqual(tasks.length);
[tuture-add] const tasks = [
[tuture-add] {
[tuture-add] id: 0,
[tuture-add] name: 'Wash the dishes',
[tuture-add] },
[tuture-add] {
[tuture-add] id: 1,
[tuture-add] name: 'Make the bed',
[tuture-add] },
[tuture-add] ];
[tuture-add]
[tuture-add] it('passes them to the Task components', () => {
[tuture-add] const toDoListInstance = shallow(<ToDoList tasks={tasks} />);
[tuture-add]
[tuture-add] toDoListInstance.find('Task').forEach((taskInstance) => {
[tuture-add] const taskProps = taskInstance.props();
[tuture-add] const matchingTask = tasks.find((task) => task.id === taskProps.id);
[tuture-add] expect(taskProps.name).toBe(matchingTask.name);
[tuture-add] });
});
});
});

通过上面的测试代码,我们可以确定 Task 组件从 ToDoList 收到了正确的 Props。由于 toDoListInstancetaskInstance 都是继承自 Enzyme 浅包装器 ShallowWrapper,因此可以调用 props 方法来获取一个组件传入的 Props。不仅如此,我们还可以检查组件状态,甚至更改状态。想要查看可用函数的完整列表,可以浏览 Enzyme 文档

通过 mount 进行完全渲染测试

但是,如果我们想测试 Task 组件中 li 标签的实际内容呢?也许我们可以像下面这样写:

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);
}

如果运行测试,我们会遇到一个错误:

FAIL app/components/ToDoList/ToDoList.test.js

● ToDoList component › when provided with array of tasks › passes them to the Task components

expect(received).toBe(expected) // Object.is equality

Expected: "Wash the dishes"
Received: "<Task />"

23 | const matchingTask = tasks.find(task => task.id === taskProps.id);
24 | const listItem = taskInstance.first('li');
\> 25 | expect(listItem.text()).toBe(matchingTask.name);
​ | ^
26 | })
27 | })
28 | });

这里之所以失败,是因为浅层渲染 shallow 的局限性:子组件 Task 将根本不会渲染,因此就无法判断是否渲染出正确的内容。

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

src/TodoList.test.js查看完整代码
import React from 'react';
[tuture-del]import { shallow } from 'enzyme';
[tuture-add]import { shallow, mount } from 'enzyme';

import ToDoList from './ToDoList';

describe('ToDoList component', () => {
// ...
[tuture-add]
[tuture-add] it('contains a matching number of <li> elements', () => {
[tuture-add] const toDoListInstance = mount(<ToDoList tasks={tasks} />);
[tuture-add]
[tuture-add] toDoListInstance.find('Task').forEach((taskInstance) => {
[tuture-add] const taskProps = taskInstance.props();
[tuture-add] const matchingTask = tasks.find((task) => task.id === taskProps.id);
[tuture-add] const listItem = taskInstance.first('li');
[tuture-add] expect(listItem.text()).toBe(matchingTask.name);
[tuture-add] });
[tuture-add] });
});
});

上面的代码会将完整的 ToDoList 和它所有子组件一起渲染。因此,之前失败的测试现在就会通过。

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

如果你不了解单元测试和集成测试这两个术语,可以看下本系列第一篇教程。

在测试与 DOM 的交互或者在处理高阶组件时,mount 函数也可以派上用场。Mount 使用 DOM 实现的模拟,Jest 默认使用的是 jsdom。我们可以通过调整 **testEnvironment 属性更改。

尝鲜 Jest Snapshot 测试

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

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

src/TodoList.test.js查看完整代码
// ...

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

describe('when provided with an array of tasks', () => {
// ...
[tuture-add]
[tuture-add] it('should render correctly', () => {
[tuture-add] const toDoListInstance = shallow(<ToDoList tasks={tasks} />);
[tuture-add] expect(toDoListInstance).toMatchSnapshot();
[tuture-add] });
});
});

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

src/__snapshots__/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

实际上,目前 CRA 默认会在监听模式下运行 Jest,我们可以一个个更新冲突的快照。首先运行 npm test ,然后输入 i 以交互方式更新失败的快照。官方的 Jest 文档提供了一个动画来展示这个过程:

提示

如果你的 CRA 版本比较老,可以通过 npm test -- --watchAll 来进入 Jest 监听模式。

小结

在本文中,我们介绍了如何直接去测试组件的 Props,并学习了 mount 函数和浅层渲染之间的区别。除此之外,我们还介绍了 Jest 快照测试,这是一个非常强大的工具,可以追踪组件渲染方式的变化。在接下来的文章中,我们还将介绍测试中常见的 Mock 技巧——与组件的模拟交互,不见不散!

图雀社区 微信公众号

扫一扫关注上方公众号,拉学习群和答疑解惑

  • 本文作者: 图雀社区
  • 本文链接: https://tuture.co/2020/08/27/387f95a/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!