继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

React 测试库(RTL)—— 第一部分

largeQ
关注TA
已关注
手记 987
粉丝 92
获赞 585
React测试库(RTL)和Jest的快速入门

首先,你需要知道Testing Library 包含多个库,帮助你模拟用户操作来测试UI组件。

照片由Ferenc AlmasiUnsplash拍摄。

Testing Library 并不是专门为 React 设计的,但在 React 社区中很受欢迎并且越来越受到关注。

作为一般规则,最好别用这个测试库来测试,

  1. 组件内部的状态
  2. 组件内部的方法
  3. 组件生命周期中的方法
  4. 子组件

我们为什么要用 Jest 和 Testing Library 呢?

由于Testing Library不是一个测试框架,所以你需要用到Jest(或者其他框架比如Mocha)来收集并适当地运行测试文件。

换句话说,Jest 会确保,当你运行 npm run test 时,所有的测试文件都会被运行,并且结果会显示在终端里。

测试概述

如果你使用的是 React v16 或更高版本,首先安装 React 的测试库:

# 以下为安装命令,保持原样即可。
npm install --save @testing-library/react
npm install --save-dev @testing-library/react @testing-library/dom # 安装 React 和 DOM 的测试库

假设一个初始的React应用如这样的所示

    // src/App.js

    function App() {  
      return (  
        <div className="App">  
          <header className="App-header">  
            <img src={logo} className="App-logo" alt="logo" />  
            <p>  
              编辑 <code>src/App.js</code> 并保存来重新加载。  
            </p>  
            <a  
              className="App-link"  
              href="https://reactjs.org"  
              target="_blank"  
              rel="noopener noreferrer"  
            >  
              学习 React 了解更多  
            </a>  
          </header>  
        </div>  
      );  
    }

有两种开始写测试的方法:

  1. 将测试文件 App.test.js 放在它的源文件(如 App.js)附近。
  2. 创建一个 src/__tests__ 文件夹,并将所有测试文件放进去。

无论如何,这样做比较好:将测试文件命名为源文件,并在其后添加 .test.[扩展名]。扩展名可以为 js, jsx, tstsx

我会按照第一个约定行事。

    // 从'@testing-library/react'导入render和screen
    import { render, screen } from '@testing-library/react';
    import App from './App';

    test('显示学习React的链接', () => {
      render(<App />);
      const linkElement = screen.getByText(/学习React/i);
      expect(linkElement).toBeInTheDocument();
    });

下面的代码是用于测试React应用的一个案例,结构如下:

  1. test 函数定义了一个名为 “renders learn react link” 的测试用例。
  2. render(<App />); 这一行渲染了 App 组件以进行测试。它基本上创建了一个 DOM 表示的副本。
  3. const linkElement = screen.getByText(/learn react/i); 这一行使用 getByText 来查找匹配正则表达式 /learn react/i 的元素。我们知道目前页面上只有一个这样的元素。
  4. 最后,通过使用 expect(linkElement).toBeInTheDocument(); 来测试该元素是否在文档中。语法中的最后部分是一个匹配器,用于检查某个内容,在这种情况下是 .toBeInTheDocument()。你可以在这里找到更多关于 Jest 匹配器RTL 匹配器 的信息,了解更多详情。

这是一个使用RTL的基础测试。

瑞典测试一个简单的表单

用 React Testing Library 测试一个简单的表单

让我们在 src/components/UserForm.js 创建一个简单的用户表单,然后在 App.js 里引入它。

    // src/components/UserForm.js  

    import React, { useState } from "react";  

    const UserForm = ({ onUserAdd }) => {  
      const [name, setName] = useState("");  
      const [email, setEmail] = useState("");  

      const handleSubmit = (e) => {  
        e.preventDefault();  
        onUserAdd({ name, email });  
        setName("");  
        setEmail("");  
      };  

      return (  
        <form onSubmit={handleSubmit}>  
          <div>  
            <label>  
              姓名:  
              <input  
                type="text"  
                value={name}  
                onChange={(e) => setName(e.target.value)}  
                required  
                placeholder="请输入姓名"  
              />  
            </label>  
          </div>  
          <div>  
            <label>  
              邮箱:  
              <input  
                type="email"  
                value={email}  
                onChange={(e) => setEmail(e.target.value)}  
                required  
                placeholder="请输入邮箱"  
              />  
            </label>  
          </div>  
          <button type="submit">提交</button>  
        </form>  
      );  
    };  

    export default UserForm;

正如前面所说,我们会在源代码文件附近创建一个名为 UserForm.test.js 的测试文件,比如 src/components/UserForm.test.js

我们想要确保有两个带有正确标签的字段,并且点击提交会触发 onUserAdd 函数。

试一下表单里的字段

我们从导入必要的库和工具以及需要测试的组件开始。

    // UserForm.test.js  

    import React from "react";  
    import { render, screen } from "@testing-library/react";  
    import UserForm from "./UserForm";

最简单来说,我们可以写一个测试来验证表单上有两个带有正确标签的字段,如下。

    // UserForm.test.js

    import React from "react";
    import { render, screen } from "@testing-library/react";
    import UserForm from "./UserForm";

    const mockOnUserAdd = jest.fn(); // 模拟用户添加函数

    test("进行测试渲染两个带有正确标签的输入框,且每个输入框都有相应的类型", () => {
      render(<UserForm onUserAdd={mockOnUserAdd} />);

      const nameInput = screen.getByLabelText("Name:"); // 获取名称输入框
      expect(nameInput).toBeVisible(); // 验证名称输入框可见
      expect(nameInput).toHaveAttribute("type", "text"); // 验证名称输入框具有属性类型为"text"

      const emailInput = screen.getByLabelText(/Email:/i); // 获取电子邮件输入框,进行不区分大小写的搜索
      expect(emailInput).toBeVisible(); // 验证电子邮件输入框可见
      expect(emailInput).toHaveAttribute("type", "email"); // 验证电子邮件输入框具有属性类型为"email"
    });

因为我们将 onUserAdd 作为属性传递,我们需要模拟实现该函数以便能够单独测试组件。只需这样做:const mockOnUserAdd = jest.fn();

在测试中,我们使用 render 函数将 React 组件渲染到虚拟 DOM 中,这是测试前必不可少的一步。在测试环境中安排组件,并对其操作和断言结果也需要这一步。

由于我们将函数传递给组件,因此我们需要传递模拟函数来模仿真实函数的行为,并断言它是否被调用,被调用的次数和参数等(稍后再说)。

我们还使用 React Testing Library 提供的 screen 工具,查询新组件在用户操作时的表现(在虚拟 DOM 内)。

通过使用代码 screen.getByLabelText('Name:'),RTL 会寻找带有用户可见标签文本 'Name:' 的元素。

我们希望输入位于文档中,并且也期望该元素具有正确的类型,即 textemail。分别是文本类型 text 和电子邮件类型 email

最好你应该经常跑测试,使用TDD或者使用npm run test -a来监控测试,这样每次相关文件有改动时,测试套件都会自动运行。

测试点击

我们来试试点击提交按钮吧。

由于我们已经在之前的测试中定义了模拟的函数,我们可以像之前那样渲染UserForm组件。

我们依然获取名字和电子邮件输入,就像之前一样。我们使用 screen.getByRole('button', { name: 'Submit' }); 来获取带有“Submit”文本的按钮元素。

    // UserForm.test.js  

    ...  

    test("在点击提交按钮时调用 onUserAdd", () => {  
      render(<UserForm onUserAdd={mockOnUserAdd} />);  

      const nameInput = screen.getByLabelText(/Name:/i);  
      const emailInput = screen.getByLabelText(/Email:/i);  
      const submitButton = screen.getByRole("button", { name: "Submit" });  

      fireEvent.change(nameInput, { target: { value: "John Doe" } });  
      fireEvent.change(emailInput, { target: { value: "john.doe@example.com" } });  

      fireEvent.click(submitButton);  

      expect(mockOnUserAdd).toHaveBeenCalledTimes(1); // 期望 mockOnUserAdd 被调用一次  
    });

我们使用 React Testing Library 的 fireEvent 来模拟浏览器中可能会发生的实际事件,像点击按钮这样的操作。记得在引入时加上 fireEvent

然后,fireEvent.change 模拟 DOM 的变化。在上述示例中,我们选择一个输入元素并更改其值。

最后,我们用 fireEvent.click(submitButton); 来模拟点击按钮。

最后一行,expect(mockOnUserAdd).toHaveBeenCalledTimes(1); 断言模拟的函数被调用了一次。这可能行得通,但最好让它异步,因为点击事件不是严格的同步事件。

换句话说,事件API是同步的,因为事件触发是同步的。但是,与之相反,比如点击事件等DOM事件是异步的,因为它们由外部触发,例如用户点击。

因此,我们应该这样添加 async/await(这并不是很好,我们之后会进一步讨论一下)。

    // UserForm.test.js

    ...

    test("点击提交按钮时,调用onUserAdd", async () => {
      // 准备
      render(<UserForm onUserAdd={mockOnUserAdd} />);

      const nameInput = screen.getByLabelText(/Name:/i);
      const emailInput = screen.getByLabelText(/Email:/i);
      const submitButton = screen.getByRole("button");

      // 操作
      await fireEvent.change(nameInput, { target: { value: 'John Doe' } }); // 更改姓名输入框的值为 'John Doe'
      await fireEvent.change(emailInput, { target: { value: 'john.doe@example.com' } }); // 更改电子邮件输入框的值为 'john.doe@example.com'
      await fireEvent.click(submitButton);

      // 验证
      expect(mockOnUserAdd).toHaveBeenCalledTimes(1);
    });

请注意,我们尽量按照 Arrange、Act、Assert 方法。

  • Arrange : 准备好要测试的组件
  • Act : 处理 DOM 中的元素
  • Assert : 验证预期
React 测试的最佳实践

根据上面的测试结果,有几点好的做法值得大家注意。

所以你应该确保每次测试都能独立运行并重新开始,确保它们互不影响。应该在每次测试前后清除所有模拟,如下。

    // 这是一个用户表单的测试文件
    // UserForm.test.js

    import React from "react"; // 导入React库
    import { render, screen } from "@testing-library/react"; // 导入测试库的render和screen函数
    import UserForm from "./UserForm";

    beforeEach(() => {
      mockOnUserAdd.mockClear(); // 清除mockOnUserAdd的模拟
    });

    afterEach(() => {
      jest.clearAllMocks(); // 清除所有的模拟
    });

    ...

有些操作可能会重复,可能最好保持DRY(DRY代表不要重复自己,Don't Repeat Yourself)。

我们可以在 afterEach 之下定义一个函数,例如:const getName = () => screen.getByLabelText(/Name:/i);,然后在需要获取元素时调用它:const nameInput = getName();

这里有一些小建议,更多关于RTL(从右到左)的内容将会陆续更新。

在我的下一篇文章中,我将把 fireEvent 更改为 userEvent。通常推荐使用 userEvent,因为它有助于编写更贴近用户实际操作的测试,这使得测试更接近实际用户行为,因此更加强大和可靠。

相关链接:Jest matchersRTL matchers.

React 测试库 (RTL) — 第 2 部分: 快速入门和 userEventlevelup.gitconnected.com
React Testing Library (RTL) 实践技巧 — 第 3 部分 (Part 3) 使用 React Testing Library (RTL) Playground 可以帮助你更容易地理解你正在编写的测试。
打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP