蓝戒博客

  • 首页
  • 研发说
  • 架构论
  • 效能录
  • AI谈
  • 随笔集
智构苍穹
AI为翼,架构为骨,文化为魂,实践探新境,价值筑长青。
  1. 首页
  2. 效能录
  3. 正文

从零到一:前端 TypeScript 工具函数库的 Jest 单元测试实战经验分享

2025年9月26日 127点热度 2人点赞 0条评论

写在前面:本文内容源于我自己在前端 TS 工具函数库项目中实践 Jest 的经验,并结合社区最佳实践整理而成。因为还在不断学习中,如果有不足之处,欢迎大家批评指正。


前言

前端开发中,TypeScript 工具函数库是很多项目的基础设施模块,比如常用的数据处理、日期处理、格式化、缓存等工具函数。如果没有良好的测试覆盖,这些基础功能出问题时,排查成本非常高。

Jest 是目前前端领域非常流行的单元测试框架,它不仅支持 TypeScript,也提供了 Mock、快照(Snapshot)、覆盖率(Coverage)统计等功能,非常适合工具函数库的测试。在这篇文章中,我将以我自己的工具函数库为例,完整分享如何从零搭建 Jest 单元测试环境,并附带最佳实践与实战示例。


一、为什么选择 Jest

在决定使用 Jest 之前,我曾对比过几个主流测试框架:

  1. Mocha + Chai:配置灵活,但需要自己选插件,TypeScript 支持需要额外配置。
  2. AVA:轻量快速,但社区生态相对小。
  3. Jest:开箱即用、TypeScript 支持完善、内置 Mock、覆盖率统计和 jsdom 环境,非常适合前端工具库。

经过实践,Jest 的优势非常明显:

  • 内置 ts 支持,无需额外配置 Babel(使用 ts-jest 可以更高级)。
  • 快照测试 功能非常适合 UI 组件,但工具函数库也可利用快照做 JSON 输出校验。
  • 内置 Mock 功能,可以模拟全局对象,比如 localStorage、fetch。
  • 覆盖率统计 友好,能直观看到函数覆盖率,方便优化测试用例。

因此,本篇文章围绕 Jest + TypeScript 的组合展开。


二、项目依赖安装

首先,我们来看看项目需要的依赖。我自己在工具库项目里使用了如下开发依赖:

JSON
"devDependencies": {
  "@testing-library/jest-dom": "^6.4.5",
  "@types/jest": "^28",
  "@umijs/test": "^4",
  "babel-jest": "^29.7.0",
  "babel-plugin-transform-inline-environment-variables": "^0.4.4",
  "cross-env": "^7.0.3",
  "dotenv": "^16.4.5",
  "jest": "^28",
  "jest-environment-jsdom": "^29.7.0",
  "jsdom-global": "^3.0.2",
  "jsdom-worker": "^0.3.0",
  "mock-local-storage": "^1.1.24",
  "ts-node": "^10",
  "typescript": "~5.3.3"
}

然后在 package.json 中添加测试命令:

JSON
"scripts": {
  "test": "jest",
  "test:cov": "yarn test --collectCoverage"
}

小提示:我习惯在 CI 环境使用 test:cov 来收集覆盖率,这样可以在 PR 时一目了然哪些函数没有被测试到。


三、Jest 配置详解

工具函数库一般没有 UI 依赖,但我们需要模拟浏览器环境(jsdom)、fetch、localStorage 等全局对象。下面是我整理的配置:

1. jest.config.ts

TypeScript
import { Config, createConfig } from '@umijs/test';

export default {
  ...createConfig(),
  collectCoverageFrom: ['src/func/**/*.{ts,js,tsx,jsx}'],
  testMatch: ['<rootDir>/test/**/*.spec.ts'],
  setupFiles: ['mock-local-storage', 'jsdom-worker'],
  setupFilesAfterEnv: ['<rootDir>/test/config/jest-setup.ts'],
  clearMocks: true,
  testEnvironment: './test/config/enhanceJsdomEnvironment.ts',
  globals: {
    fetch,
    Request,
  },
  testTimeout: 30000,
  coveragePathIgnorePatterns: [
    '<rootDir>/src/func/workerHelper.ts'
  ],
} as Config.InitialOptions;

配置解读

  • collectCoverageFrom:指定需要收集覆盖率的文件。
  • testMatch:指定测试文件匹配规则,我使用 test/**/*.spec.ts。
  • setupFiles:Jest 启动时先执行的文件,这里加载 localStorage 和 jsdom-worker。
  • setupFilesAfterEnv:在测试运行环境初始化后执行的文件,用于设置 jest-dom。
  • testEnvironment:自定义环境,增强了 jsdom 支持 fetch 等。
  • globals:全局变量注入。
  • testTimeout:设置单测超时时间,避免异步测试卡住。
  • coveragePathIgnorePatterns:忽略部分文件的覆盖率统计,比如 Web Worker 文件。

2. test/config/jest-setup.ts

TypeScript
import '@testing-library/jest-dom';
import 'dotenv/config';

这里我加载了 jest-dom 来增强 DOM 断言,以及 dotenv 读取环境变量。

3. test/config/enhanceJsdomEnvironment.ts

TypeScript
import JsdomEnvironment from 'jest-environment-jsdom';

export default class EnhanceJsdomEnvironment extends JsdomEnvironment {
  constructor(...args: ConstructorParameters<typeof JsdomEnvironment>) {
    super(...args);

    this.global.fetch = fetch;
    this.global.Headers = Headers;
    this.global.Request = Request;
    this.global.Response = Response;
  }
}

通过继承 jsdom,我们可以在单测中使用原生 fetch 和 Request/Response 类,方便测试 API 调用。

4. test/utils/mock-storage.ts

TypeScript
global.window = {} as (Window & typeof globalThis);
import 'mock-local-storage';
window.localStorage = global.localStorage;
window.sessionStorage = global.sessionStorage;

解决 Node 环境下 localStorage 不存在的问题。

5. test/utils/test-utils.ts

TypeScript
/**
 * 等待指定的毫秒数
 * @param ms - 等待的毫秒数
 * @returns {Promise<void>}
 */
function wait(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export { wait };

实用工具函数,方便异步测试等待。


四、实战示例

假设我们的工具函数库有一个 math.ts,内容如下:

TypeScript
// src/func/math.ts
export function add(a: number, b: number) {
  return a + b;
}

export function subtract(a: number, b: number) {
  return a - b;
}

对应的单测写法:

TypeScript
// test/func/math.spec.ts
import { add, subtract } from '../../src/func/math';

describe('math utils', () => {
  test('add should return correct sum', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
  });

  test('subtract should return correct difference', () => {
    expect(subtract(5, 2)).toBe(3);
    expect(subtract(2, 5)).toBe(-3);
  });
});

运行命令:

Bash
yarn test

输出:

Markdown
 PASS  test/func/math.spec.ts
  math utils
    ✓ add should return correct sum (3 ms)
    ✓ subtract should return correct difference

五、测试异步工具函数

假设我们有一个异步工具函数 delay.ts:

TypeScript
// src/func/delay.ts
export async function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

对应测试:

TypeScript
// test/func/delay.spec.ts
import { delay } from '../../src/func/delay';
import { wait } from '../utils/test-utils';

describe('delay util', () => {
  test('should resolve after specified ms', async () => {
    const start = Date.now();
    await delay(100);
    const duration = Date.now() - start;
    expect(duration).toBeGreaterThanOrEqual(100);
  });

  test('wait util should wait correct time', async () => {
    const start = Date.now();
    await wait(50);
    const duration = Date.now() - start;
    expect(duration).toBeGreaterThanOrEqual(50);
  });
});

结合 wait 工具函数可以更方便地测试异步逻辑。


六、Mock 全局对象

在工具库中,经常会操作 localStorage 或 fetch,Jest 提供了 Mock 功能:

TypeScript
// test/func/storage.spec.ts
describe('localStorage utils', () => {
  beforeEach(() => {
    localStorage.clear();
  });

  test('should set and get item', () => {
    localStorage.setItem('key', 'value');
    expect(localStorage.getItem('key')).toBe('value');
  });
});

对于 fetch:

TypeScript
// test/func/fetch.spec.ts
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ data: 123 }),
  } as any)
);

test('fetch returns mocked data', async () => {
  const res = await fetch('/api/test');
  const data = await res.json();
  expect(data).toEqual({ data: 123 });
});

Mock 可以让测试完全脱离网络依赖,提高稳定性。


七、覆盖率统计

运行:

Bash
yarn test:cov

输出示例:

Markdown
----------|----------|----------|----------|----------|----------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Lines |
----------|----------|----------|----------|----------|----------------|
 math.ts  |      100 |      100 |      100 |      100 |                |
 delay.ts |      100 |      100 |      100 |      100 |                |
----------|----------|----------|----------|----------|----------------|

覆盖率报告可以帮助我们发现未测试的边界条件或异常逻辑。


八、最佳实践分享

在长期实践中,我总结了几个最佳实践:

  1. 独立、纯函数优先:工具函数尽量无副作用,便于测试。
  2. 每个函数至少一个测试用例:覆盖正常、边界、异常三种情况。
  3. 使用 Mock 全局对象:避免依赖真实网络或浏览器 API。
  4. 覆盖率不等于质量:100% 覆盖率不代表测试充分,关注边界和异常逻辑。
  5. 按功能组织测试文件:保持 test/func 与 src/func 一致。
  6. CI 集成:在 CI 中跑 yarn test:cov,保证 PR 不降低覆盖率。
  7. 异步测试要注意超时:尤其是网络或定时器相关逻辑。
  8. 善用工具函数:比如封装 wait、mockFetch,减少重复代码。

九、个人经验和反思

坦白讲,刚开始配置 Jest 的时候,我踩了很多坑:

  • Node 环境下没有 window 或 localStorage。
  • 异步测试超时导致 CI 失败。
  • Web Worker 文件无法被 Jest 识别。

通过上面的配置,基本解决了这些问题,但仍有提升空间:

  • 对 Worker 文件的覆盖率仍不完善。
  • 对复杂依赖的 Mock 有时需要手动注入。
  • 当工具函数涉及第三方 API 时,Mock 设计要仔细,否则测试不稳定。

总结一句:单测搭建是一件耐心活,稳扎稳打比追求花哨技巧更重要。


十、结语

本文分享了前端 TypeScript 工具函数库如何从零搭建 Jest 单元测试环境,包括:

  • 依赖安装与配置
  • jsdom 与全局对象 Mock
  • 同步与异步函数测试
  • 覆盖率统计与报告
  • 最佳实践与经验分享

希望对正在尝试给工具库、前端基础库、或者小型项目搭建 Jest 单测的小伙伴们有所帮助。

作为一个还在不断学习的前端开发者,我很愿意把自己的实践经验分享出来,也希望大家在评论区交流踩坑心得,一起进步。

最后附上参考资料:

  • Jest 官方文档
  • TypeScript 官方文档
  • Testing Library 文档
标签: Jest
最后更新:2025年9月26日

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

最新 热点 随机
最新 热点 随机
npm 安全更新:把握令牌变更与发布体系的迁移参考指南 TresJS:用 Vue 构建现代化交互式 3D 体验 i18n 高效实现方案:前端国际化神器安利一波 前端国际化 i18n 实践:从项目到组件库的全链路方案 GEO(生成引擎优化)完整指南:AI 搜索时代的企业内容新机会 NativeScript:用 JavaScript / TypeScript 构建真正的原生应用
前端开源工具 PinMe:极简部署体验分享大屏适配的核心痛点与一行 autofit 解决方案markdown-exit:现代化的 Markdown 解析工具Lerna + Monorepo:前端多仓库管理的最佳实践CrewAI:基于角色协作的 AI Agent 团队框架浅析2025 最推荐的 uni-app 技术栈:unibest + uView Pro 高效开发全攻略
echarts.js多图表数据展示使用小结 从零到一:前端 TypeScript 工具函数库的 Jest 单元测试实战经验分享 全面理解WebSocket与Socket、TCP、HTTP的关系及区别 免费开源托管服务全解析:手把手教你用 Vercel + GitHub 一键部署前端项目 jquery插件Bootstrap Table使用方法详解 移动端微信分享弹出遮罩层js效果
最近评论
渔夫 发布于 1 个月前(11月05日) 学到了,感谢博主分享
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8

COPYRIGHT © 2025 蓝戒博客_智构苍穹-专注于大前端领域技术生态. ALL RIGHTS RESERVED.

京ICP备12026697号-2