手摸手,打造属于自己的 React 组件库02 — 测试篇

1,705 阅读4分钟

第二部分 - 测试篇:给组件加上单元测试

原文链接

组件库现在由我们团队在维护,欢迎围观,Start ~

引言

在第一部分,我们基于 create-react-app 构建了项目的基础结构。对于一个组件库来说,不仅要有简单,实用的组件,每一个组件的质量也是至关重要。而单元测试,正是提升软件质量的一种有效的手段。在本文中,不仅会在之前的项目中完成单元测试的配置,还会带着大家一起走进单元测试的世界~

教程部分

本篇文章,是这个系列的第二篇 - 单元测试

2020到了,我还需要单元测试吗?

首先,告诉大家一个小秘密,就是我不太喜欢单元测试。它很费时,重构项目会变得很吃力。而且,单元测试没有一个明确的边界,有时总会觉得自己的单元测试写的不够。而且,代码也并不会因为单元测试就不会出现 BUG,即使你的测试覆盖率达到了💯%。

值得幸运的是,对于 React 应用来说,单元测试的成本正在逐步降低。

优秀的 TypeScript

首先,在 TypeScript 出现之后,我们不需要去写大部分的单元测试。

举个简单的例子,这是 TypeScript 的实现:

type Props = {
  name: string;
};

而下面的这段代码,是单元测试的版本:

import React from 'react';
import Component from './Component';
test('does not do something completely embarassing if I forgot to pass a name', () => {
  const { getByText } = render(<Component />);
  expect(getByText('Name: ')).not.toBeInTheDocument();
});

所以,TypeScript 的类型系统的出现,已经不再需要这部分的单元测试了。

ESLint 和 Prettier

Eslint 会分析代码,并根据一组规则对其进行检查。这听起来也很像测试。而 Prettier 可以根据一组规则来约束代码风格。并且,在编写代码的时候,配合编辑器就可以得到即时的反馈了。

像用户一样编写测试

上面所提到的技术,更多是从语言,代码质量,代码风格的角度来提升软件的质量,但却没有像一个用户一样来使用我们的软件。毕竟,软件怎么使用,只有开发者清楚。

除了编写文档、注释、编写 Demo。单元测试也更像是软件的一种说明书,为软件的使用提供了保障。

React 文档 中所描述的一样,本文也只是讨论:渲染组件树,在一个简化的测试环境中渲染组件树并对它们的输出做断言检查。

技术栈

  • Jest:JavaScript 测试框架。
  • @testing-library/react:将 React 组件转化成 Dom 节点来测试,而不是渲染的 React 组件的实例,可以当做是 Enzyme 的替代。
  • ts-jest:是一个 TypeScript 预处理器,它支持 Jest 的源映射,允许你使用 Jest 测试用 TypeScript 编写的项目。
  • jest-html-reporter:一个生成测试报告的库。

项目配置

因为之前使用的是 react-app-rewired 作为 Cli 工具,它会对 Jest 的版本有所限制。所以,下面安装依赖时,有些库会锁住版本。

npm i -D ts-jest@24.1.0 @types/jest jest@24.9.0 jest-html-reporter @testing-library/react @babel/preset-env @babel/preset-react

为了使用安装的这些依赖,接下来需要修改下 package.json ,增加测试,以及提交之前的校验。

...
  "scripts": {
+    "test": "jest --no-cache",
  }
...
  "lint-staged": {
    "*.{js,ts,tsx}": [
       "eslint --fix src/**/*.{ts,tsx}",
+      "jest --bail --coverage --findRelatedTests",
       "git add ."
    ],
    "*.{md,css,html,less}": [
       "prettier --write",
       "git add ."
    ]
  }

最后,还需要给 Jest 增加一个配置文件:jest.config.js

module.exports = {
  preset: 'ts-jest',
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  globals: {
    'ts-jest': {
      babelConfig: {
        presets: ['@babel/preset-env', '@babel/preset-react'],
      },
    },
  },
  testEnvironment: 'jsdom',
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{ts,tsx,js,jsx,mjs}',
    '<rootDir>/src/**/?(*.)(spec|test).{ts,tsx,js,jsx,mjs}',
  ],
  reporters: [
    'default',
    [
      './node_modules/jest-html-reporter',
      {
        pageTitle: '测试报告',
      },
    ],
  ],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  collectCoverage: true,
  collectCoverageFrom: ['app/react/**/*.{ts,tsx}', '!app/react/__tests__/api/api-test-helpers.ts'],
};

编写测试用例

这里,我需要给之前添加的 EmptyLine,增加测试用例。

empty-line/index.tsx
import './style/index.less';
import EmptyLine from './EmptyLine';

export default EmptyLine;
empty-line/EmptyLine.tsx
import React from 'react';

export interface IEmptyLineProps {
  height?: number;
}

const EmptyLine = ({ height = 20 }: IEmptyLineProps) => {
  return <div className="d-empty-line" style={{ height }} />;
};

export default EmptyLine;
empty-line/test/index.test.tsx
import React from 'react';
import { create, act } from 'react-test-renderer';
import EmptyLine from '../EmptyLine';

test('默认高度渲染正常', () => {
  const emptyLine = create(<EmptyLine />).toJSON();

  expect(emptyLine?.props.style).toEqual(
    expect.objectContaining({
      height: 20,
    }),
  );
});

test('自定义高度渲染正常', () => {
  let emptyLine: any;

  act(() => {
    emptyLine = create(<EmptyLine height={30} />);
  });

  expect(emptyLine.toJSON()?.props.style).toEqual(
    expect.objectContaining({
      height: 30,
    }),
  );
});

下面,就是两个 EmptyLine 常见的使用场景,已经在我们的单元测试中都覆盖到了。

需要注意的是,这里的 EmptyLine.tsx 组件中并没有引入样式,而是在同级的 index.tsx 中引入了样式。因为,这里样式在测试的时候是不支持引入的,会出问题。

运行 npm run test 就可以看到结果:

image

当我们提交代码,commit 的时候,也会在根目录生成一个测试报告:

image

结束语

到这里,我们给组件库增加了测试的功能。使我们的组件库也变得更加有保障,高大上了起来。最后,在第三章中,我们会聊聊如何打包,并上传至 NPM 的事情,敬请期待吧。