使用 React Testing Library 和 Jest 完成单元测试

本文介绍了如何使用React Testing Library和Jest为React应用编写单元测试,强调了测试的重要性和不同测试级别。文章通过一个计数器组件的测试例子,对比了Enzyme和React Testing Library的优劣,推荐使用React Testing Library,因为它更注重用户角度的测试。文章遵循了AAA模式(编排、执行、断言)进行测试,并提供了8个具体的测试示例,包括快照测试、DOM元素测试、事件测试、异步操作测试、Redux测试、Context测试、Router测试和HTTP请求测试,帮助读者掌握React应用的单元测试实践。
摘要由CSDN通过智能技术生成

引言

在2020的今天,构建一个 web 应用对于我们来说,并非什么难事。因为有很多足够多优秀的的前端框架(比如 ReactVueAngular);以及一些易用且强大的UI库(比如 Ant Design)为我们保驾护航,极大地缩短了应用构建的周期。

但是,互联网时代也急剧地改变了许多软件设计,开发和发布的方式。开发者面临的问题是,需求越来越多,应用越来越复杂,时不时会有一种失控的的感觉,并在心中大喊一句:“我太南了!”。严重的时候甚至会出现我改了一行代码,却不清楚其影响范围情况。这种时候,就需要测试的方式,来保障我们应用的质量和稳定性了。

接下来,让我们学习下,如何给 React 应用写单元测试吧

需要什么样的测试

软件测试是有级别的,下面是《Google软件测试之道》一书中,对于测试认证级别的定义,摘录如下:

  • 级别1

    • 使用测试覆盖率工具。
    • 使用持续集成。
    • 测试分级为小型、中型、大型。
    • 创建冒烟测试集合(主流程测试用例)。
    • 标记哪些测试是非确定性的测试(测试结果不唯一)。
  • 级别2

    • 如果有测试运行结果为红色(失败)就不会发布。
    • 每次代码提交之前都要求通过冒烟测试。(自测,简单走下主流程)
    • 各种类型的整体代码覆盖率要大于50%。
    • 小型测试的覆盖率要大于10%。
  • 级别3

    • 所有重要的代码变更都要经过测试。
    • 小型测试的覆盖率大于50%。
    • 新增重要功能都要通过集成测试的验证。
  • 级别4

    • 在提交任何新代码之前都会自动运行冒烟测试。
    • 冒烟测试必须在30分钟内运行完毕。
    • 没有不确定性的测试。
    • 总体测试覆盖率应该不小于40%。
    • 小型测试的代码覆盖率应该不小于25%。
    • 所有重要的功能都应该被集成测试验证到。
  • 级别5

    • 对每一个重要的缺陷修复都要增加一个测试用例与之对应。
    • 积极使用可用的代码分析工具。
    • 总体测试覆盖率不低于60%。
    • 小型测试代码覆盖率应该不小于40%。

小型测试,通常也叫单元测试,一般来说都是自动化实现的。用于验证一个单独的函数,组件,独立功能模块是否可以按照预期的方式运行。

而对于开发者来说,重要的是进行了测试的动作。本篇文章主要围绕着React组件单元测试展开的,其目的是为了让开发人员可以站在使用者的角度考虑问题。通过测试的手段,确保组件的每一个功能都可以正常的运行,关注质量,而不是让用户来帮你测试。

在编写单元测试的时候,一定会对之前的代码反复进行调整,虽然过程比较痛苦,可组件的质量,也在一点一点的提高。

技术栈选择

当我们想要为 React 应用编写单元测试的时候,官方推荐是使用 React Testing Library + Jest 的方式。Enzyme 也是十分出色的单元测试库,我们应该选择哪种测试工具呢?

下面让我们看一个简单的计数器的例子,以及两个相应的测试:第一个是使用 Enzyme 编写的,第二个是使用 React Testing Library 编写的。

counter.js
// counter.js
import React from "react";

class Counter extends React.Component {
  state = { count: 0 };
  increment = () => this.setState(({ count }) => ({ count: count + 1 }));
  decrement = () => this.setState(({ count }) => ({ count: count - 1 }));
  render() {
    return (
      <div>
        <button onClick={this.decrement}>-</button>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

export default Counter;
counter-enzyme.test.js
// counter-enzyme.test.js
import React from "react";
import { shallow } from "enzyme";

import Counter from "./counter";

describe("<Counter />", () => {
  it("properly increments and decrements the counter", () => {
    const wrapper = shallow(<Counter />);
    expect(wrapper.state("count")).toBe(0);

    wrapper.instance().increment();
    expect(wrapper.state("count")).toBe(1);

    wrapper.instance().decrement();
    expect(wrapper.state("count")).toBe(0);
  });
});
counter-rtl.test.js
// counter-rtl.test.js
import React from "react";
import { render, fireEvent } from "@testing-library/react";

import Counter from "./counter";

describe("<Counter />", () => {
  it("properly increments and decrements the counter", () => {
    const { getByText } = render(<Counter />);
    const counter = getByText("0");
    const incrementButton = getByText("+");
    const decrementButton = getByText("-");

    fireEvent.click(incrementButton);
    expect(counter.textContent).toEqual("1");

    fireEvent.click(decrementButton);
    expect(counter.textContent).toEqual("0");
  });
});

比较两个例子,你能看出哪个测试文件是最好的嘛?如果你不是很熟悉单元测试,可能会任务两种都很好。但是实际上 Enzyme 的实现有两个误报的风险:

  • 即使代码损坏,测试也会通过。
  • 即使代码正确,测试也会失败。

让我们来举例说明这两点。假设您希望重构组件,因为您希望能够设置任何count值。因此,您可以删除递增和递减方法,然后添加一个新的setCount方法。假设你忘记将这个新方法连接到不同的按钮:

counter.js
// counter.js
export default class Counter extends React.Component {
  state = { count: 0 };
  setCount = count => this.setState({ count });
  render() {
    return (
      <div>
        <button onClick={this.decrement}>-</button>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

第一个测试(Enzyme)将通过,但第二个测试(RTL)将失败。实际上,第一个并不关心按钮是否正确地连接到方法。它只查看实现本身,也就是说,您的递增和递减方法执行之后,应用的状态是否正确。
这就是代码损坏,测试也会通过

现在是2020年,你也许听说过 React Hooks,并且打算使用 React Hooks 来改写我们的计数器代码:

counter.js
// counter.js
import React, { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count => count + 1);
  const decrement = () => setCount(count => count - 1);
  return (
    <div>
      <button onClick={decrement}>-</button>
      <p>{count}</p>
      <button onClick={increment}>+</button>
    </div>
  );
}

这一次,即使您的计数器仍然工作,第一个测试也将被打破。Enzyme 会报错,函数组件中无法使用state:

ShallowWrapper::state() can only be called on class components

接下来,就需要改写单元测试文件了:

counter-enzyme.test.js
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐是自己的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值