jest 初识 与react + ts + jest 项目实践

2,182 阅读7分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

前言

这篇笔记主要用于记录在学习 jest 测试框架过程中的一些关键的知识点和自己的一些粗浅的理解,以及利用 jestReact + ts 项目添加单元测试的开发实践。

为什么需要单元测试

单元测试在代码开发过程中是很重要的,像后端开发等都会要求保证代码的测试覆盖率,但是前端对单元测试的运用却比较少。虽然引入单元测试需要我们写更多的测试代码,但是也具备很多好处:

  • 当我们的代码越来越庞大,模块越分越细时,单元测试能够帮助我们快速的定位出问题的代码。
  • 单元测试也是保证代码质量的一种手段。
  • 驱动开发:我们在写开发代码的同时更多的考虑如何进行测试能够让开发效率更高。

单元测试从概念上可分为两种类型:

  • TDD (Test-Driven Development)(常用类型): 测试驱动开发,即我需要的结果是什么,如果不是就是错误的。
  • BDD (Behavior-Driven Development)(实际上难以应用): 行为驱动开发,从具体功能的角度出发看。即结果应该是什么,如果不是什么就出错。

当然,编写单元测试也要遵循以下原则:

  • 测试代码要易于修改和维护。
  • 保证每次只测单一的点,避免测试中逻辑过于复杂。
  • 要尽量贴近真实。

jest 的核心内容

jest 作为测试框架之一,必然拥有测试框架所具备的一些核心内容:

  1. 测试的执行方法
  2. 断言库 (检查执行结果)
  3. mock库 (屏蔽会产生影响的外部依赖)
  4. test runner (测试运行器)
  5. 覆盖率工具 (检查测试代码对真实代码的覆盖率)

诸如test runner和覆盖率工具,一般位于我们测试框架的底层(还没接触),这里不会过多介绍,这里主要讲下前三个内容。

测试的执行方法

jest 通过 test 方法执行测试代码,我们需要在测试代码中写完整的测试逻辑,保证我们测试的完整性。

test('test unit name', () => {
  // 执行测试代码
});

断言库

断言库是检查测试代码执行结果的关键,我们的测试代码通过与否都由断言库判决。

jest 提供了丰富的断言工具:

  • toBe(value):使用 Object.is 判断结果应该为某个指定value
  • toEqual(value):一般用来判断对象有相同的 property
  • .toContain(item): 一般用于判断数组或字符串的子集。 ...等等

mock库

mock库是很强大的功能,能够帮助我们在测试代码中屏蔽外部依赖的影响,它可以实现以下功能:

  • mock 一个模块或方法:我们并不获取依赖的真实内容,而只记录对模块或方法的调用记录。
  • mock 方法的执行结果:不执行真实的方法,只产出我们想要的输出。
  • mock 方法的执行过程:不执行真实的代码逻辑,只执行我们我们假定的过程。

mock 是非常有用的,在我们的真实场景中,能够帮助我们 mock api 请求,mock 第三方库等等,那些我们不需要在测试代码中真正去做的事情都可以交给 mock 库,相当于欺骗我们的测试代码已经执行这样的动作,而不需要真正的做到

snapshot 测试

snapshot 测试对于不是纯js的项目是很重要的,它能够对我们的React组件渲染结果生成 snapshot(快照),以检验我们的渲染结果是否匹配相同的 snapshot

jest 项目实践

项目集成 jest

我们的项目是 React + ts 的,构建工具为 webpack, 接下来我们将通过以下步骤将 jest 测试框架集成到我们的项目中。

安装 jest

yarn add --dev jest

因为我们的项目用到了ts,而 jest 是执行在 node 环境的, 需要使用到 ts-node 和 类型检查等,因此我们还需要安装 ts 版的 jest ,并初始化一个相关的配置文件 jest.config.ts

yarn add --dev ts-jest

yarn ts-jest config:init

支持 React 组件测试

接下来我们还需要安装一些依赖以支持对 React component 的测试:

  • react-test-renderer:可以生成 componentsnapshot,进行 snapshot testing
  • @testing-library/react 和 enzyme:添加对组件的 DOM 元素进行测试所必须的依赖,可以二选一。
yarn add --dev react-test-renderer

yarn add --dev @testing-library/react

yarn add --dev enzyme

支持 ts

由于项目中采用 ts ,我们需要安装支持 ts 类型的依赖:


yarn add --dev @types/jest
yarn add --dev @types/react-test-renderer

安装完毕,接下来就可以开始对我们的项目编写 test code,进行测试了!

React 组件测试

接下来我们结合 react-test-rendererReact 组件进行 snapshot test

在项目中存在 move.tsx 文件,定义了 MoveBox 组件:

// move.tsx

import React from 'react';

interface Props {}

export const MoveBox = (props: Props): React.ReactElement => {
  return <div className="box-out move"></div>;
};

在被测试组件的同级文件夹中创建 __test__ 文件夹,文件夹下创建以 React 文件名称为前缀的测试文件: move.test.tsx,编写测试代码如下,react-test-renderer 提供的 renderer 方法可以模拟 React 组件渲染。

jest 提供的 toMatchSnapshot API 能够在第一次运行测试时,在__snapshots__文件夹下生成一个对应的快照文件: move.test.tsx.snapshot,之后每次执行测试会检验结果是否符合快照文件的内容,如果React 组件修改导致渲染结果改变,则不会匹配对应的快照,即测试不通过。

// move.test.tsx.snapshot

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`test Movebox component render test Movebox component render success 1`] = `
<div
  className="box-out move"
/>
`;
// move.test.tsx
import React from 'react';
import renderer from 'react-test-renderer';
import { MoveBox } from '../move';

// describe 描述一组测试
describe('test Movebox component render', () => {
  it('test Movebox component render success', () => {
    // renderer.create 模拟 React 渲染组件,并将结果输出为 json 字符串。
    const moveComp = renderer.create(<MoveBox></MoveBox>).toJSON();
    // 期望 moveComp 匹配生成的快照文件。
    expect(moveComp).toMatchSnapshot();
  });
});

对纯ts代码的方法进行测试

在项目的alignInRange.ts中存在由纯 ts 编写的函数 alignInRange,该函数保证输出的结果在最大值和最小值限定的区间内:

export function alignInRange(value: number, minValue: number, maxValue: number): number {
  if (value < minValue) {
    return minValue;
  }
  if (value > maxValue) {
    return maxValue;
  }
  return value;
}

创建 __test__ 文件夹,文件夹下创建以方法名称为前缀的测试文件: alignInRange.test.ts,编写测试代码如下:

import { alignInRange } from '../alignInRange';

// 最小值和最大值的区间为[5,30],输入的 value 为10,期望输出结果为 10
test('test alignInRange return value', () => {
  expect(alignInRange(10, 5, 30)).toBe(10);
});
// 最小值和最大值的区间为[10,30],输入的 value 为5,期望输出结果为限定范围内的最小值 10
test('test alignInRange return minValue', () => {
  expect(alignInRange(5, 10, 30)).toBe(10);
});
// 最小值和最大值的区间为[10,30],输入的 value 为50,期望输出结果为限定范围内的最大值 30
test('test alignInRange return maxValue', () => {
  expect(alignInRange(50, 10, 30)).toBe(30);
});

在运行测试代码之前,如果希望可以看到覆盖率结果报告,需要在 jest.config.js 中添加配置项 collectCoveragetrue

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  collectCoverage: true, // 输出测试覆盖率报告
};

运行yarn test 指令,执行测试代码,输出结果如下:

image.png

完美!

总结

完成一个项目的单元测试覆盖,需要结合实际项目,编写大量的测试代码和完善更多的细节,以发挥 jest 作用。希望通过本文对 jest 的知识点的介绍,以及集成 jest 的一些实践,能够对你的开发实践有所帮助!