【译】使用Enzyme和React Testing Library测试React Hooks

原文:https://css-tricks.com/testing-react-hooks-with-enzyme-and-react-testing-library/

当你开始在应用中使用React Hooks时,你需要确保编写的代码是可靠的。确保代码没有bug的一种方法就是编写测试用例。测试React hooks与测试一般程序的方式没有太大区别。

在本教程中,我们将了解如何通过使用带有hooks的to-do应用程序来实现这一点。我们将介绍使用 EnzymeReact 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;

使用Enzyme测试

在开始测试之前,我们需要安装这些包。是时候启动终端了!

npm install --save-dev enzyme enzyme-adapter-16

在src目录中,创建一个名为 setupTests.js 的文件。这是我们用来配置 Enzymeadapter

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

Test 1: 组件渲染

为此,我们将使用浅渲染。浅渲染允许我们检查组件的渲染方法是否被调用——这是我们想要确认的,因为这里我们需要证明组件渲染。

it("renders", () => {
  shallow(<Todo />);
});

Test 2: 展示初始待办事项

这里我们将使用 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() 方法设置输入字段的值。我们使用断言,在进一步模拟单击事件之前,输入“修复失败测试”,该事件应该将新的项目添加到待办事项列表中。

最后,断言列表中有三个项,并且第三个项与我们创建的项相等。

Test 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() 方法,该方法将删除被单击的item。然后检查我们拥有的item的数量,并且返回的的值。

这四个测试的源代码可以在GitHub上找到。

使用react-testing-library测试

我们将为此写三个测试:

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 1: 初始待办事项的渲染

我们将在 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则通过。

Test 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() 方法,该方法返回的是文本与我们传的参数匹配的节点。然后,我们可以通过检查子数组的长度来检查待办事项的长度。

Test 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 返回删除按钮的节点。因为我们只想删除一个项目,所以我们对集合中的第一个项目触发一个click事件,它应该删除第一个待办事项。这应该使待办事项子节点的长度等于1。

这些测试也可以在GitHub上找到。

语法检查

当使用hooks时,有两个语法检查规则要遵守:

规则1:在顶层调用钩子

...循环或嵌套函数,而不是内部条件。

// Don't do this!
if (Math.random() > 0.5) {
  const [invalid, updateInvalid] = useState(false);
}

这违反了第一条规则。根据官方文档,React取决于钩子调用的关联状态和相应的 useState 调用的顺序。这段代码打乱了顺序,因为钩子只有在条件为true时才会被调用。

这也适用于 useEffect 和其他钩子。查看文档了解更多细节。

规则2:从React功能组件调用钩子

钩子用于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代码!

React钩子和应用中的其他钩子一样容易出错,你要确保你能很好地使用它们。正如我们刚才看到的,有几种方法可以做到这一点。无论你是使用Enzyme或是enzyme与React Testing Library其中之一来写测试完全取决于你。不管怎样,试着使用linting,毫无疑问,你会很高兴你这样做了。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章