当您开始在应用程序中使用 React Hooks 时,您需要确保编写的代码非常可靠。发布有错误的代码可不是什么好事。确保代码无错误的一种方法是编写测试。测试 React Hooks 与一般测试 React 应用程序的方式并没有太大区别。
在本教程中,我们将通过一个使用 Hooks 构建的待办事项应用程序来了解如何做到这一点。我们将介绍如何使用 Ezyme 和 React Testing Library 编写测试,这两个库都可以做到这一点。如果您不熟悉 Enzyme,我们实际上之前发布过一篇关于它的文章,展示了 如何在 React 应用程序中与 Jest 一起使用它。在深入研究测试 React Hooks 之前,检查一下这个文章是个不错的想法。
我们要测试的内容
一个非常标准的待办事项组件看起来像这样
import React, { useState, useRef } from "react";
const Todo = () => {
const [todos, setTodos] = useState([
{ id: 1, item: "Fix bugs" },
{ id: 2, item: "Take out the trash" }
]);
const todoRef = useRef();
const removeTodo = id => {
setTodos(todos.filter(todo => todo.id !== id));
};
const addTodo = data => {
let id = todos.length + 1;
setTodos([
...todos,
{
id,
item: data
}
]);
};
const handleNewTodo = e => {
e.preventDefault();
const item = todoRef.current;
addTodo(item.value);
item.value = "";
};
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<h2>Add Todo</h2>
</div>
</div>
<form>
<div className="row">
<div className="col-md-6">
<input
type="text"
autoFocus
ref={todoRef}
placeholder="Enter a task"
className="form-control"
data-testid="input"
/>
</div>
</div>
<div className="row">
<div className="col-md-6">
<button
type="submit"
onClick={handleNewTodo}
className="btn btn-primary"
>
Add Task
</button>
</div>
</div>
</form>
<div className="row todo-list">
<div className="col-md-6">
<h3>Lists</h3>
{!todos.length ? (
<div className="no-task">No task!</div>
) : (
<ul data-testid="todos">
{todos.map(todo => {
return (
<li key={todo.id}>
<div>
<span>{todo.item}</span>
<button
className="btn btn-danger"
data-testid="delete-button"
onClick={() => removeTodo(todo.id)}
>
X
</button>
</div>
</li>
);
})}
</ul>
)}
</div>
</div>
</div>
);
};
export default Todo;
使用 Enzyme 进行测试
在开始测试之前,我们需要安装这些包。是时候启动终端了!
npm install --save-dev enzyme enzyme-adapter-16
在 src
目录中,创建一个名为 setupTests.js 的文件。我们将使用它来配置 Enzyme 的适配器。
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
现在我们可以开始编写测试了!我们想测试四件事
- 组件是否渲染
- 渲染时是否显示初始待办事项
- 我们能否创建一个新的待办事项并获得另外三个待办事项
- 我们能否删除其中一个初始待办事项,并只留下一个待办事项
在 src
目录中,创建一个名为 __tests__
的文件夹,并在其中创建用于编写 Todo 组件测试的文件。让我们将该文件命名为 Todo.test.js。
完成此操作后,我们可以导入所需的包并创建一个 describe
块,在其中填写我们的测试。
import React from "react";
import { shallow, mount } from "enzyme";
import Todo from "../Todo";
describe("Todo", () => {
// Tests will go here using `it` blocks
});
测试 1:组件渲染
为此,我们将使用浅渲染。浅渲染允许我们检查组件的 render 方法是否被调用——这就是我们在这里要确认的内容,因为这是我们需要证明组件渲染的证据。
it("renders", () => {
shallow(<Todo />);
});
测试 2:显示初始待办事项
这里我们将使用 mount
方法,它允许我们比 shallow
提供的更深入。这样,我们可以检查待办事项的长度。
it("displays initial to-dos", () => {
const wrapper = mount(<Todo />);
expect(wrapper.find("li")).toHaveLength(2);
});
测试 3:我们可以创建一个新的待办事项并获得另外三个待办事项
让我们考虑一下创建新待办事项的过程
- 用户在输入字段中输入值。
- 用户点击提交按钮。
- 我们总共有三个待办事项,其中第三个是新创建的。
it("adds a new item", () => {
const wrapper = mount(<Todo />);
wrapper.find("input").instance().value = "Fix failing test";
expect(wrapper.find("input").instance().value).toEqual("Fix failing test");
wrapper.find('[type="submit"]').simulate("click");
expect(wrapper.find("li")).toHaveLength(3);
expect(
wrapper
.find("li div span")
.last()
.text()
).toEqual("Fix failing test");
});
我们挂载组件,然后使用 find()
和 instance()
方法设置输入字段的值。在进一步模拟点击事件之前,我们断言输入字段的值设置为“修复失败的测试”,这应该将新项目添加到待办事项列表中。
最后,我们断言列表中有三个项目,并且第三个项目等于我们创建的项目。
测试 4:我们可以删除其中一个初始待办事项,并只留下一个待办事项
it("removes an item", () => {
const wrapper = mount(<Todo />);
wrapper
.find("li button")
.first()
.simulate("click");
expect(wrapper.find("li")).toHaveLength(1);
expect(wrapper.find("li span").map(item => item.text())).toEqual([
"Take out the trash"
]);
});
在这种情况下,我们通过对第一个项目模拟点击事件来返回待办事项。预期这将调用 removeTodo()
方法,该方法应该删除被点击的项目。然后我们检查我们拥有的项目数量以及返回的项目的价值。
这四个测试的源代码可以在 GitHub 上查看。
使用 react-testing-library 进行测试
我们将为此编写三个测试
- 初始待办事项是否渲染
- 我们能否添加一个新的待办事项
- 我们能否删除一个待办事项
让我们首先安装所需的包
npm install --save-dev @testing-library/jest-dom @testing-library/react
接下来,我们可以导入包和文件
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Todo from "../Todo";
import "@testing-library/jest-dom/extend-expect";
test("Todo", () => {
// Tests go here
}
测试 1:渲染初始待办事项
我们将在 test
块中编写我们的测试。第一个测试将如下所示
it("displays initial to-dos", () => {
const { getByTestId } = render(<Todo />);
const todos = getByTestId("todos");
expect(todos.children.length).toBe(2);
});
这里发生了什么?我们使用 getTestId
返回 data-testid
与传递给方法的 data-testid
匹配的元素的节点。在本例中,是 <ul>
元素。然后,我们检查它是否总共有两个子元素(每个子元素都是无序列表内的 <li>
元素)。由于初始待办事项等于两个,因此这将通过。
测试 2:我们可以添加一个新的待办事项
我们也在这里使用 getTestById
返回与我们传入的参数匹配的节点。
it("adds a new to-do", () => {
const { getByTestId, getByText } = render(<Todo />);
const input = getByTestId("input");
const todos = getByTestId("todos");
input.value = "Fix failing tests";
fireEvent.click(getByText("Add Task"));
expect(todos.children.length).toBe(3);
});
我们使用 getByTestId
返回输入字段和 ul
元素,就像我们之前做的那样。为了模拟添加新待办事项的点击事件,我们使用 fireEvent.click()
并传入 getByText()
方法,该方法返回文本与我们传递的参数匹配的节点。从那里,我们可以通过检查子元素数组的长度来检查待办事项的长度。
测试 3:我们可以删除一个待办事项
这将有点类似于我们之前做的事情
it("deletes a to-do", () => {
const { getAllByTestId, getByTestId } = render(<Todo />);
const todos = getByTestId("todos");
const deleteButton = getAllByTestId("delete-button");
const first = deleteButton[0];
fireEvent.click(first);
expect(todos.children.length).toBe(1);
});
我们使用 getAllByTestId
返回删除按钮的节点。由于我们只想删除一个项目,因此我们在集合中的第一个项目上触发点击事件,这应该会删除第一个待办事项。然后这将使 todos
子元素的长度等于 1。
这些测试也可以在 GitHub 上找到。
代码风格检查
在使用 Hooks 时,需要遵守两条代码风格检查规则
规则 1:在顶层调用 Hook
…而不是在条件语句、循环或嵌套函数内部。
// Don't do this!
if (Math.random() > 0.5) {
const [invalid, updateInvalid] = useState(false);
}
这违反了第一条规则。根据官方文档,React 依赖于 Hook 的调用顺序来关联状态和相应的useState
调用。此代码破坏了顺序,因为只有在条件为真时才会调用 Hook。
这也适用于useEffect
和其他 Hook。查看文档以获取更多详细信息。
规则 2:从 React 函数组件中调用 Hook
Hook 旨在用于 React 函数组件中,而不是 React 的类组件或 JavaScript 函数中。
我们基本上已经介绍了在代码检查方面不应该做什么。我们可以使用一个npm 包来避免这些错误,该包专门用于执行这些规则。
npm install eslint-plugin-react-hooks --save-dev
以下是如何添加到包的配置文件中以使其生效
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
如果您正在使用Create React App,那么您应该知道该包从v3.0.0版本开始就支持 lint 插件。
开始编写可靠的 React 代码吧!
React Hook 与应用程序中的任何其他内容一样,也容易出错,您需要确保正确使用它们。正如我们刚刚看到的,我们可以通过几种方法来实现这一点。您可以使用Enzyme或React Testing Library来编写测试,这完全取决于您。无论哪种方式,请尝试在编写代码时使用代码检查,毫无疑问,您会庆幸自己这么做了。
安装 enzyme-adapter-16 时,是否应该为 enzyme-adapter-react-16?
您好,Kingsley。我最近也开始测试我的 Hook。我找不到使用 Enzyme 和 React Testing Library 的充分理由。在本文的开头或引言中链接的文章 (“使用 Jest 和 Enzyme 为 React 应用程序编写测试”) 中解释为什么使用它们会很好。
快速说明:只需读取 event.target 而不是使用 todoRef。
您好,Kingsley。感谢您的文章。这真的帮了我很大的忙。
我有一点不明白的是,为什么 Enzyme 有 4 个测试,而 React-Testing-Library 有 3 个?
很棒的文章,对我的 React-testing-library 入门很有帮助。需要更新的一件事是“test”和“it”的嵌套。
最新版本明确表示不要测试测试。因此,每个测试都应该内联使用“test(...)”。
抱歉,我的意思是“不要嵌套测试”。
我想知道 React 测试库的测试。测试 ToDo 的“标题”是否在 DOM 中不是更好?这样我们就可以像用户行为一样进行测试,我们看到了 ToDo 的标题,而不是
<ul>
标签的子元素长度。初始 ToDo
“修复 Bug”在文档中
“倒垃圾”在文档中
添加 ToDo
“添加任务”在文档中
删除 ToDo
“修复 Bug”不在文档中
您如何看待这种方法?