原文:https://css-tricks.com/testing-react-hooks-with-enzyme-and-react-testing-library/
当你开始在应用中使用React Hooks时,你需要确保编写的代码是可靠的。确保代码没有bug的一种方法就是编写测试用例。测试React hooks与测试一般程序的方式没有太大区别。
在本教程中,我们将了解如何通过使用带有hooks的to-do应用程序来实现这一点。我们将介绍使用Enzyme和React Testing Library编写测试,这两个库都能做到这一点。如果你第一次使用Enzyme,我们之前发布过关于它的文章,《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;
在开始测试之前,我们需要安装这些包。是时候启动终端了!
npm install --save-dev enzyme enzyme-adapter-16
在src目录中,创建一个名为setupTests.js
的文件。这是我们用来配置Enzyme
的adapter
。
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
现在我们可以开始写我们的测试代码了!我们想要测试四点:
1、组件渲染
2、渲染时初始待办事项的展示
3、我们可以创建一个新的待办事项然后返回三个待办事项
4、我们可以删除一个初始的待办事项并且只留下一个
在你的src目录中,创建一个名为 tests 的文件夹,并创建一个文件,你可以在其中编写待办事项组件的测试。我们将该文件命名为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
});
为此,我们将使用浅渲染。浅渲染允许我们检查组件的渲染方法是否被调用——这是我们想要确认的,因为这里我们需要证明组件渲染。
it("renders", () => {
shallow(<Todo />);
});
这里我们将使用mount
方法,它允许我们深入到比shallow
更深的地方。这样,我们就可以检查待办事项的长度。
it("displays initial to-dos", () => {
const wrapper = mount(<Todo />);
expect(wrapper.find("li")).toHaveLength(2);
});
#### Test 3: 我们可以创建一个新的待办事项然后返回三个待办事项
让我们想一下创建一个新的待办事项的过程:
1、用户在input
中输入一个值。
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()
方法设置输入字段的值。我们使用断言,在进一步模拟单击事件之前,输入“修复失败测试”,该事件应该将新的项目添加到待办事项列表中。
最后,断言列表中有三个项,并且第三个项与我们创建的项相等。
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()
方法,该方法将删除被单击的item。然后检查我们拥有的item的数量,并且返回的的值。
这四个测试的源代码可以在GitHub上找到。
我们将为此写三个测试:
1、初始待办事项的渲染
2、我们可以加一个新的待办事项
3、我们可以删除一个待办事项
首先,我们安装需要的安装包:
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
}
我们将在test模块
里写我们的测试代码。第一个测试是这样的:
it("displays initial to-dos", () => {
const { getByTestId } = render(<Todo />);
const todos = getByTestId("todos");
expect(todos.children.length).toBe(2);
});
这里发生了什么?我们使用getTestId
来返回元素的与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
来返回input和ul元素。为了模拟添加新待办项的单击事件,我们使用fireEvent.click()
方法并传入getByText()
方法,该方法返回的是文本与我们传的参数匹配的节点。然后,我们可以通过检查子数组的长度来检查待办事项的长度。
这个看起来有点像我们之前写的:
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
返回删除按钮的节点。因为我们只想删除一个项目,所以我们对集合中的第一个项目触发一个click事件,它应该删除第一个待办事项。这应该使待办事项子节点的长度等于1。
这些测试也可以在GitHub上找到。
当使用hooks时,有两个语法检查规则要遵守:
...循环或嵌套函数,而不是内部条件。
// Don't do this!
if (Math.random() > 0.5) {
const [invalid, updateInvalid] = useState(false);
}
这违反了第一条规则。根据官方文档,React取决于钩子调用的关联状态和相应的useState
调用的顺序。这段代码打乱了顺序,因为钩子只有在条件为true时才会被调用。
这也适用于useEffect
和其他钩子。查看文档了解更多细节。
钩子用于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,那么你应该知道,从3.0.0版本开始,该包就支持开箱即用的lint插件。
React钩子和应用中的其他钩子一样容易出错,你要确保你能很好地使用它们。正如我们刚才看到的,有几种方法可以做到这一点。无论你是使用Enzyme或是enzyme与React Testing Library其中之一来写测试完全取决于你。不管怎样,试着使用linting,毫无疑问,你会很高兴你这样做了。