【📔 前端小笔记】在React工程中食用Jest单测能力

950 阅读7分钟

🧪 前端实验室:偏向于前端研究领域和方案总结,🍚 前端大食堂:专注于前端横向对比和评测,📔 前端小笔记 :对前端基础知识和经验的小总结

为什么需要测试

在不断迭代的开发过程中,如何保障我们的核心功能不受影响,回归校验能顺利通过呢?

答案可能是 测试很多测试尽可能面面俱到的测试

封面图放了一个典型的 测试金字塔 的图,从底至上分别是 单元测试视觉测试集成测试。

所以,我们该从哪里开始 🤔 ?我们先仔细看看各类测试的区别:

单元测试 / Unit Testing

  • 易于编写且易于运行。
  • 保证程序输入输出是符合预期的。
  • 如果你几乎没有时间编写测试,那么它们应该是你的首选。

视觉测试 / UI Testing

  • 即使你有 100% 的单元测试覆盖率,这并不意味着组件在设备和浏览器上看起来很好,比如某个元素的宽高发生了细微的变化,又或者按钮的响应逻辑产生了变化。
  • 视觉测试的目的在于还原真实的浏览器环境,让代码的视觉呈现变得可控。

集成测试 / Integration Tests

  • 即使两个组件有单元测试和可视化测试,也不能保证它们能一起工作。
  • 在更大的范围,集成测试确保一个系统的各个单元能紧密合作,按照预期完成任务。
  • e2eTest 端到端测试也可以纳入集成测试的范围。

一目了然,对于我这种完全没有接触过测试框架,又想开始写一些测试的菜鸟而言,完全可以无负担地,先从接入单元测试开始。

这里我们选择Jest作为我们的单元测试框架,附上当前流行的测试框架的 npm trend :

image.png

Jest 是什么?

Jest 是一款优雅、简洁的 JavaScript 测试框架。

Jest 支持 BabelTypeScriptNodeReactAngularVue 等诸多框架!

\

由于 Jest 和 React一样,其开源组织都是 Facebook 公司,所以如果工程项目基于React开发,那么 Jest 几乎就是最好的配套测试框架。

如果你的工程不支持ts,那么按照 Jest的官方起步文档 来安装jest即可。

如果需要支持ts,且需要ts的类型校验,这里推荐安装 ts-jest,除了安装和创建config时有所不同之外,使用方法并不会与 jest 有什么差异。

using npmusing yarn
Prerequisitesnpm i -D jest typescriptyarn add --dev jest typescript
Installingnpm i -D ts-jest @types/jestyarn add --dev ts-jest @types/jest
Creating confignpx ts-jest config:inityarn ts-jest config:init
Running testsnpm tor npx jestyarn testor yarn jest

测试需要做什么?(wip)

对于一个完全只有一丢丢测试经验的前端同学而言,刚起步一般关心以下三个能力:

  • 断言(核心功能,校验输入输出)
  • 异步(针对请求或者其他异步场景)
  • 代码覆盖率(covrage,衡量测试工程的完备性)

以上功能Jest都提供了内置的功能支持,更复杂的场景可以自行探索。

1、断言

常规代码

// 判断一个值是否为True
export const isTrue = (target: any) => {
  return Boolean(target) && target !== "false";
};

测试代码

describe("isTrue", () => {
  test("case1", () => {
    expect(isTrue("true")).toEqual(true);
  });
  test("case2", () => {
    expect(isTrue("false")).toEqual(false);
  });
});

2、异步

异步请求代码

export const requestMock = () => {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        resolve({ statu: "200" });
      }, 1000);
    } catch (err) {
      reject(err);
    }
  });
};

测试代码

可以在代码中使用 awaitasync。也可以在测试返回的Promise里获取data,注意这里不要漏了 return。

describe("Request", () => {
  test("async & await", async () => {
    await expect(requestMock()).resolves.toEqual({ statu: "200" });
  });

  test("promise", () => {
    return requestMock().then((data) => {
      expect(data).toEqual({ statu: "200" });
    });
  });
});

3、覆盖率

写完基础的测例之后,我们如果想要了解单测的覆盖率,该怎么做呢?

命令行输入 npx jest --coverage 并回车执行,jest 会基于config来寻找项目中的测例并全量执行,最后会输出这样的一份覆盖率报告:

image.png

查看测试结果

Jest 会在控制台输出每一个测例的名称校验结果以及耗时

image.png

哪些场景下适合写单测?

当业务需求不紧急的时候,完善单测可以保障代码质量,大家也都愿意做。

当业务需求特别紧急的时候,写单测的时间成本就是不得不考虑的问题。

那么哪些场景下,即使业务需求紧急,也可以考虑写单测呢?

  1. 业务核心模块
  2. 数据链路比较复杂的场景

这两类的需求,往往调试和返工的成本非常高。

如果我们事先通过单测和类型推断,解决80%以上的隐藏问题,开发的体验就会非常丝滑,单测的时间成本问题也就迎刃而解。

Mocking & Transform

CSSMocking

npm install -D identity-obj-proxy
// package.json (for CSS Modules)
{
  "jest": {
    "moduleNameMapper": {
      "\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      "\.(css|less)$": "identity-obj-proxy"
    }
  }
}

在样式对象上,你的所有 className 都会原样返回 (如 styles.foobar === 'foobar'

SVGMocking

在根目录下创建一个 svgTransform.js 文件

module.exports = {
  process() {
    return "module.exports = {};";
  },
  getCacheKey() {
    // The output is always the same.
    return "svgTransform";
  },
};

jest.config.js中配置 svg 的 transform 规则

module.exports = {
  ...
  transform: {
    "^.+\.(ts|js|html)$": "ts-jest",
    "^.+\.svg$": "<rootDir>/svgTransform.js",
  },
};

JsDOM

安装 test-library/jest-dom,该库提供了一组可用于扩展jest的自定义jest匹配器。

npm install --save-dev @testing-library/jest-dom
// In your own jest-setup.js (or any other name)
import '@testing-library/jest-dom'

// In jest.config.js add (if you haven't already)
setupFilesAfterEnv: ['<rootDir>/jest-setup.js']

接下来就可以开始写测试用例了👏

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

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

Awesom Jest Resources

这里只举了两个,其他可见 awesome-jest

1. Jest-electron

《jest + electron 基础实践——jest-electron》

将 jest 的单测代码放到 electron(底层是 chrome)中运行,在浏览器中运行node代码,获得较为完善的UI测试能力。

具体痛点举例:canvas 渲染UI测试、交互动画、multi-render

2. Jest-puppeteer

《Jest - Using with Puppeteer》

让puppeteer被jest所驱动

可以完成的操作:

  1. e2e,在无头浏览器上进行UI测试
  2. 借用puppeteer的能力,完成浏览器操作模拟(如页面跳转、单击按钮、填写表格)、断言页面包含的文本、页面结构测试、创建页面截图等。

一些常见问题的探索 🤔

Jest 的冷启动时间有点长怎么办

  1. --watch / --watchAll 启动 jest 的 watch mode,二次启动的速度会比较快,这也算是曲线解决问题的一种方法吧
  2. --runInBand (别名 -i )
  1. 在当前进程中连续运行所有测试,而不是创建运行测试的子进程的工作池
  2. 该功能减少的是创建进程池的时间,当调试几个单测的时候比较有用,但是如果测试的数量比较大,效率就不如开启多进程的策略

Monorepo工程的Jest配置策略

  1. 可以在root下配置一份通用的 jest.config.js,包含各类 jest 的配置值
  2. 如果每一个 package/project 需要自定义的 jest config,Jest Config 提供了 projects 配置项,在root下的 jest.config.js 里配置后,Jest 会在所有定义的 project 里尝试运行测例,详见 Jest - project 配置项说明
module.exports = {
  projects: [
    '<rootDir>/packages/projectA/jest.config.js',
    '<rootDir>/packages/projectB/jest.config.js'
  ],
};

搭配食用的VSCODE插件 🛠

Jest Run It

image.png

一个 VS Code 扩展,可帮助您从编辑器运行和调试 Jest 测试。您不再需要为您更改的那个测试运行整个测试套件🎉

  • 提供测试用例列表面板
  • 可以在每个测试代码文件里,单独运行和调试测例
  • 文件级运行调试的Action图标

image.png

如果你不想在浏览器里调试代码,希望在编辑器就能得到完整的调试体验,用它吧!

遇到的一些小问题

TypeError: Jest: a transform must export a process function. 

  1. 检查 jestts-jest 的版本是否一致
  2. 清除安装包,重新安装最新版本

参考文章

  1. jestjs.io/zh-Hans/
  2. 《React - 测试环境》
  3. 《掘金 - 如何使用Jest做单元测试》
  4. 《知乎 - 前端测试框架jest 》
  5. 《Developers - How to Test a Component Library》