前端单测学习(2)—— react 组件单测初步

1,761 阅读7分钟

前言

上一篇中我们介绍了一些单测的基础知识,并且创建了一个demo工程,这一篇我们来写一些react组件的单测的初步介绍

引入组件库

基于我们上一个博客里面的项目,为了让我们的demo项目更加好看,更加贴近我们实际项目,我们这里引入antd这个UI库,这里我们采用了pnpm,其他同学可以根据自己的习惯用yarn或者用npm

pnpm add antd

image.png 添加成功之后在package.json的dependencies看到已经添加了antd image.png 这里使用的vscode,会有检查拼写,可以看到上面的antd是出现了波浪线,不影响使用但是有这个波浪线提示看着不太舒服,这里就处理一下,打开settings,输入cSpell.words,添加antd即可 image.png 可以看到已经没有波浪线提示了

image.png

改写入口

原先的入口App.tsx里面的内容是默认生成的内容,其实就是create-react-app默认的入口内容,我们改造一下,试试引入antd的组件,让入口尽可能贴近我们实际的项目 改写App.tsx,我们去除掉原先的那些,改为如下,就是场景的一个按钮然后点击后有message提示,这里注意一个点,原先默认的App.tsx是有一个import React from 'react',但是当你使用了React 17+版本之后只要tsconfig中配置一下就可以不用默认引入这个,笔者初始化项目的时候tsconfig已经有这个配置了,比较旧的版本可能要留意一下"jsx": "react-jsx"这个配置

import "./App.css";
import { Button, message } from "antd";
import { useCallback } from "react";

function App() {
  const showMessage = useCallback(() => {
    message.info(`展示一个提示`);
  }, []);

  return (
    <div className="App">
      <Button type="primary" onClick={showMessage}>
        这是一个按钮
      </Button>
    </div>
  );
}

export default App;

为了使用antd的样式,我们需要引入对应的样式,并且根据我们的需要改写一下App.css

@import '~antd/dist/antd.css';
.App {
  text-align: center;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

看一下效果,按钮居中,点击有弹出 image.png 原先项目里面的App.test.tsx现在也不能用了,因为入口文件已经被我们改写了,所以这时候这个App.test.tsx运行会报错,我们的vscode在这里的时候也会波浪线提示报错,我们运行看看

image.png

可以看到这时候运行报错,这也是给我们的项目带来一些启示,单测的存在可以保证项目运行的准确,尤其是我们在做一些break change或者重构之后,单测能够保证新改动的功能能够覆盖到之前的功能。

这里改写一下App.test.tsx

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

test("renders learn react link", () => {
  render(<App />);
  const linkElement = screen.getByText("这是一个按钮");
  expect(linkElement).toBeInTheDocument();
});

重新运行一下pnpm test,这个时候运行可以通过 image.png

vscode 插件

自己本地装的vscode插件,可以参考一下 image.png

写一个用于测试的组件

我们先写一个简单的展示型的FC,然后对这个FC进行单测的编写,一边学习一边改造这个组件

cd src
mkdir components
cd components
mkdir todo-header
cd todo-header
touch index.tsx
touch index.css

我们创建一个components组件,在这个目录创建我们的TodoHeader组件 image.png index.tsx

import "./index.css";

interface TodoHeaderProps {
  // 待办事项的标题
  title: string;
}

export default function TodoHeader({ title }: TodoHeaderProps) {
  return (
    <div className="report-header">
      <span className="title">{title}</span>
    </div>
  );
}

index.css

.todo-header {
  display: flex;
}
.title {
  font-style: normal;
  font-weight: 500;
  font-size: 16px;
  line-height: 24px;
  color: #1664ff;
}

在App.tsx调用这个组件

<TodoHeader title="这是一个标题" />

这里我们走读一下这个component,这里是先写的比较简单,传入一个title标题然后用于展示,后面我们可以根据情况继续完善拓展这个组件,根据组件的各种功能进行单测的编写

看一下效果

image.png

针对上面的组件开始写单测

首先我们在components下面建一个__tests__目录,用来存放这个components下面的所有组件对应的单测文件,我们对应组件的名称创建一个todo-header.test.tsx文件,然后我们开始编写我们的第一个单测 从上面的组件分析,其实我们只有一个title的props,这里我们判断这个组件是否符合预期的话,其实就是判断我们传入的这个title的内容能否在我们的视图中正常显示出来,所以根据这个目标我们可以写一个测试集合并且做出断言 todo-header.test.tsx

import { render, screen } from "@testing-library/react";
import TodoHeader from "../todo-header";

describe("测试TodoHeader组件", () => {
  it("正确渲染title组件", () => {
    const title = "测试的标题";
    render(<TodoHeader title={title} />);
    const titleElement = screen.getByText(title);
    expect(titleElement).toBeInTheDocument();
  });
});

走读一下这段代码

  • describe 定义了一些相关测试的集合,以当前我们的这个测试文件为例,我们可以把这个组件相关的测试用例全部写在一个集合中
  • it 相当于一个测试用例,我们保证一个测试用例只测一个功能或者一个属性,就像一个函数只做一件事情意义
  • render testing-library提供的用于渲染组件的能力
  • screen 可以理解为testing-library提供给我们的一个可查询的对象,因为查询整个document.body非常常见,DOM Testing library还导出一个screen对象,其中包含预先绑定到document.body的每个查询
  • expect 一些断言,这里断言查询到的这个元素是出于这个document中

这样我们就完成了一个非常简单点的单测的编写,然后我们可以运行一下试试

image.png

单独运行一个case

我们通过运行pnpm test的话实际上是跑了全量的测试用例,当我们单独新增了一个测试用例之后我们只想跑增量的用例或者指定的用例,所以这时候就需要加参数或者配置了

通过运行时传参运行单独的case

pnpm test src/components/__tests__/todo-header.test.tsx

image.png

其他判断正确渲染的方法

通过queryByText

it("正确渲染title组件通过queryByTest", () => {
    const title = "测试的标题";
    const { queryByText } = render(<TodoHeader title={title} />);
    const titleElement = queryByText(title);
    expect(titleElement).not.toBeNull();
    expect(titleElement).toBeInTheDocument();
});

通过render暴露出来的queryByText方法来查询是否存在,这里返回的titleElement可能是null,所以我们断言不等于null,并且该节点出于document中

通过getByText

it("正确渲染title组件通过getByText", () => {
    const title = "测试的标题";
    const { getByText } = render(<TodoHeader title={title} />);
    const titleElement = getByText(title);
    expect(titleElement).toBeInTheDocument();
});

和queryByText比较像,这不过这里不会返回null,所以我们不做不等于null的断言

通过container的query

it("正确渲染title组件通过container的query", () => {
    const title = "测试的标题";
    const { container } = render(<TodoHeader title={title} />);
    const titleElement = container.querySelector("span");
    expect(titleElement).toHaveTextContent(title);
});

render会返回一个container,我们就可以通过container的querySelector方法来查询对应的节点,这里我们的title属性是处于span标签中的,而且目前有且只有一个,所以selector写的比较简单,直接写span,获得节点后断言节点的textContent等于title

通过testid查询

我们在原先的组件中增加data-testid属性,设置我们约定的id

<div className="report-header">
  <span className="title" data-testid="todo-header-title">
    {title}
  </span>
</div>
it("正确渲染title组件通过getByTestId", () => {
    const title = "测试的标题";
    const { getByTestId } = render(<TodoHeader title={title} />);
    const titleElement = getByTestId("todo-header-title");
    expect(titleElement).toHaveTextContent(title);
});

完整的测试用例

import { render, screen } from "@testing-library/react";
import TodoHeader from "../todo-header";

describe("测试TodoHeader组件", () => {
  it("正确渲染title组件", () => {
    const title = "测试的标题";
    render(<TodoHeader title={title} />);
    const titleElement = screen.getByText(title);
    expect(titleElement).toBeInTheDocument();
  });

  it("正确渲染title组件通过queryByTest", () => {
    const title = "测试的标题";
    const { queryByText } = render(<TodoHeader title={title} />);
    const titleElement = queryByText(title);
    expect(titleElement).not.toBeNull();
    expect(titleElement).toBeInTheDocument();
  });

  it("正确渲染title组件通过getByText", () => {
    const title = "测试的标题";
    const { getByText } = render(<TodoHeader title={title} />);
    const titleElement = getByText(title);
    expect(titleElement).toBeInTheDocument();
  });

  it("正确渲染title组件通过container的query", () => {
    const title = "测试的标题";
    const { container } = render(<TodoHeader title={title} />);
    const titleElement = container.querySelector("span");
    expect(titleElement).toHaveTextContent(title);
  });

  it("正确渲染title组件通过getByTestId", () => {
    const title = "测试的标题";
    const { getByTestId } = render(<TodoHeader title={title} />);
    const titleElement = getByTestId("todo-header-title");
    expect(titleElement).toHaveTextContent(title);
  });
});

运行一下结果

image.png

结尾

由于篇幅的问题这里就先写到这里,关于单测还有很多东西没有展开,这里先做个场景的介绍,后面继续补充

传送门

前端单测学习(1)—— 单测入门之react单测项目初步
前端单测学习(2)—— react 组件单测初步
前端单测学习(3)—— react组件单测进阶
前端单测学习(4)—— react 组件方法&fireEvent
前端单测学习(5)—— 快照
前端单测学习(6)—— 定时器
前端单测学习(7)—— mock
前端单测学习(8)—— react hook
前端单测学习(9)—— 覆盖率报告
前端单测学习(10)—— 状态管理redux
前端单测学习(11)—— react hook 进阶
前端单测学习(12)—— 性能优化
前端单测学习(13)—— 自动化测试