JavaScript单元测试:2026年全面指南

Unit Tests JavaScript: Comprehensive 2026 Guide

使用Capgo的2026年指南来掌握javascript单元测试。涵盖Jest、Mocha、设置、模拟、CI和Electron应用的Capacitor & Electron应用的技巧。

Martin Donadieu

Martin Donadieu

内容营销人员

Unit Tests JavaScript: Comprehensive 2026 Guide

你可能处于两种情况之一。要么你的javascript项目几乎没有测试,每次重构都感到风险很大,要么你已经有测试,但其中一半的测试很慢、脆弱且难以信任。

CapacitorElectron 应用中情况会变得更糟。一个简单的功能可能会触及共享的业务逻辑、浏览器API、原生插件、局部文件、IPC和远程服务。 如果你测试这些部分的方式不对,测试套件就变成了一个假依赖的迷宫。如果你测试它们的方式正确,你就能快速获得逻辑中出现问题的反馈。

好的单元测试 JavaScript 工作并不以聪明的匹配语法开始。它始于一个有纪律的界限:测试纯粹的逻辑直接,隔离副作用,并避免编写测试以便在重命名内部函数时立即崩溃。

目录

选择 JavaScript 测试框架

专业的 JavaScript 项目需要一个真正的测试运行器。临时脚本和手动控制台检查在多个工程师处理同一代码库时无法扩展。您需要测试发现、断言、异步处理、模拟和在本地开发和 CI 中一致地运行所有内容的方式。

当前指导方针正在趋向于小众的主流选项。 Jest、Mocha 和 Jasmine 经常被突出为主要框架, Jest 经常被突出为内置测试结构、断言、模拟和异步支持的单个包, 如本.

Pluralsight JavaScript 测试实验室

展示了流行的 JavaScript 测试框架,包括 Jest、Mocha、Cypress 和 Playwright。

The first mistake teams make is treating unit tests as a side activity. That usually leads to inconsistent file naming, custom assertions nobody remembers, and helpers that only one person understands.

A framework gives you a shared language:

  • 单元测试的第一步骤是将其视为辅助活动。通常会导致文件命名不一致、 nobody 记得的自定义断言以及只有一个人理解的助手。 测试结构 describetestit
  • 断言
  • 有可读的匹配器 钩子
  • 用于设置和清除 异步支持
  • Mocking工具 为外部依赖项

如果您的团队还需要测试自动化的更广泛视图,Capgo 有一个有用的概述 应用交付工作流程中的自动化测试.

Jest vs Mocha一目了然

Jest和Mocha代表了两种不同的哲学.

Jest 是全能选项。它带来了大多数团队在第一天所需的内容。
Mocha 更具模块化。它给了你一个运行器,并期望你自己组装剩余的堆栈。

Feature Jest Mocha
设置复杂度 大多数团队来说更低 因为你通常会添加断言和模拟库
断言 内置 通常与另一个库一起使用
模拟 内置 通常与另一个库一起使用
异步测试 内置且直接 __CAPGO_KEEP_0__
__CAPGO_KEEP_1__ __CAPGO_KEEP_2__ __CAPGO_KEEP_3__
__CAPGO_KEEP_4__ __CAPGO_KEEP_5__ __CAPGO_KEEP_6__

__CAPGO_KEEP_7__ __CAPGO_KEEP_8__

__CAPGO_KEEP_9__

__CAPGO_KEEP_10__ __CAPGO_KEEP_11__ 除非代码库已经有足够的理由继续使用 Mocha。 那个建议在应用程序包含 CapacitorElectron, 因为这些项目已经有足够的移动部分。 减少测试工具的杂乱程度会带来快速的收益。

Mocha 在较老的 Node.js 服务或长期存活的代码库中仍然有意义。 但对于一名中级工程师设置一个强大的套件从头开始,Jest 通常比它创造的摩擦要少。

一个重要的范围说明。 Cypress 和 Playwright 是优秀的工具,但它们解决了不同的问题。 他们更适合于浏览器级别和端到端检查,而不是快速的内部循环,JavaScript 工作应该居住的地方。

项目设置和您的第一个测试

一个清洁的测试设置应该是无聊的。 如果添加第一个测试感觉复杂,套件可能不会健康。

一名戴着眼镜的男子正在一台笔记本电脑上工作,坐在一张木质桌子上。

一个简单的 Jest 设置

从一个已经有一个的 JavaScript 项目开始 package.json然后添加 Jest 作为开发依赖项,并配置测试脚本。

{
  "scripts": {
    "test": "jest"
  }
}

对于许多项目来说,这已经足够了。您可以在需要时根据模块系统、转译或多包结构添加更多配置。

如果您正在本地构建一个 Capacitor 应用,并希望在添加共享逻辑的测试之前将开发环境保持良好状态,Capgo 的指南 关于设置 Capacitor 本地环境 是一个实用的伴侣。

在 code 之前编写测试。

测试优先模式不仅仅是个人偏好。美国消费者金融保护局的 JavaScript 指南明确推荐 以测试为首组织测试 describe 并以 it断言 expect(...) 为基础的检查 JavaScript 单元测试指南.

因为这样做改变了你设计 code 的方式。函数变得更小,依赖关系变得更明显,副作用不再渗透到应该保持纯粹的逻辑中。

这里是一个最小的例子:

// math.js
function addTax(amount, rate) {
  return amount + amount * rate;
}

module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');

describe('addTax', () => {
  it('returns the amount with the tax applied', () => {
    expect(addTax(100, 0.2)).toBe(120);
  });
});

每次都使用 Arrange Act Assert

这个 Arrange, Act, Assert 模式使测试保持可读,即使它们变得更加复杂。

  1. 准备 准备输入和任何需要的设置。
  2. 执行 通过调用函数。
  3. 断言 在结果上.

应用于验证辅助函数:

function isSupportedPlatform(platform) {
  return ['ios', 'android', 'web', 'desktop'].includes(platform);
}

describe('isSupportedPlatform', () => {
  it('returns true for ios', () => {
    // Arrange
    const platform = 'ios';

    // Act
    const result = isSupportedPlatform(platform);

    // Assert
    expect(result).toBe(true);
  });
});

小的测试会经久不衰。一个测试通常应该回答一个问题,而不是讲述整个工作流程.

对于Capacitor和Electron项目来说,遵守这一规则更为重要,因为你的纯粹逻辑通常与native或桌面集成放在一起code。保持业务规则在不依赖平台运行时可测试,第一条测试不会是最后有用的测试之一.

掌握Mock和异步Code

大多数应用code中的bug并不是来自于两个数字的加法。它们来自code:网络请求、文件、插件API、定时器、IPC通道、存储层等.

这就是Mock的作用。它让你控制边界,使测试可以专注于code的决策过程.

一个白板图表,展示了微服务架构、API、数据存储、外部服务和事件驱动数据流的图表.

模拟边界,而不是模拟所有内容

可维护测试指南强调 单一行为覆盖one strong assertion per test, and it also warns that overusing mocks makes tests brittle and tightly coupled to implementation details, as summarized in this TestRail article on maintainable unit tests.

TestRail

Bad target for a mock-heavy test:

  • whether helper A called helper B
  • whether service C called serializer D
  • whether an internal private function ran twice

Better target:

  • what the function returned
  • whether it handled a failed dependency correctly
  • 在JavaScript中,这个警告非常重要。团队经常会从每个导入模块中开始使用mock,并最终测试函数是否正确调用其他函数,而不是测试真实行为。

A better pattern for Capacitor and Electron code

在移动和桌面应用中,我更喜欢在native或平台API周围包裹一个wrapper层。然后,单元测试模拟wrapper,而不是平台本身。

示例结构:

// cameraGateway.js
async function getPhoto(cameraPlugin) {
  return cameraPlugin.getPhoto();
}

module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
  const photo = await cameraGateway.getPhoto();
  return { path: photo.path, ready: true };
}

module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');

test('returns mapped photo data', async () => {
  const fakeCameraGateway = {
    getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
  };

  const result = await loadProfilePhoto(fakeCameraGateway);

  expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});

这个模式也适用于Electron。Wrap ipcRenderer文件访问、shell集成等后面都有一个薄的适配器。单元测试击中服务层,而不是直接击中运行时。

对于测试Capacitor应用的发布逻辑和更新路径的团队,Capgo有一个相关的指南关于 使用模拟场景测试Capacitor OTA更新.

快速的教程可以帮助您的团队仍然在正常化异步测试风格:

测试异步流程而不出现不稳定性

在测试中使用 async/await 当code被测试返回一个Promise时。它比callback-heavy模式更清晰,并且更容易调试。

async function fetchProfile(api) {
  const response = await api.getUser();
  return response.name;
}

test('returns the user name from the API response', async () => {
  const api = {
    getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
  };

  const result = await fetchProfile(api);

  expect(result).toBe('Ava');
});

也要测试失败路径:

test('throws when the API request fails', async () => {
  const api = {
    getUser: jest.fn().mockRejectedValue(new Error('network failed'))
  };

  await expect(fetchProfile(api)).rejects.toThrow('network failed');
});

测试两条路径。生产环境中,人们通常记住的是那条不顺利的路径。

高级策略

一个测试套件在code变更后仍然有用时才算有用。这比写一堆通过的测试要难得多。

测试套件的可维护性

将测试分成预算

一本实用的指南建议将测试分成 70/20/10 单元测试、集成测试和端到端测试 ,其中单元测试提供最快的反馈和最稳定的故障。同样的指导建议,一个完整的单元测试套件理应在10秒内完成 ,而预提交检查应该保持在5秒内 under 5 seconds根据这个 OpenReplay 测试指南.

我把它当作一个预算工具,而不是一种宗教。如果大部分的努力都花在了端到端测试上,团队会等待太久才能获得反馈。如果所有的测试都是单元测试,你就会错过真正的系统边界。

对于一个 Capacitor 或 Electron 应用程序,一种健康的平衡通常如下:

  • 单元测试 用于价格逻辑、权限规则、序列化、更新资格、特性标志和状态变换
  • 集成测试 用于存储适配器、插件包装器和 IPC 协议
  • E2E 测试 用于几个关键的旅程,如登录、购买流程、同步或更新提示

覆盖率是一个手电筒,而不是目标

覆盖率报告在帮助你发现重要逻辑中未测试的 branch 时是有用的。它们会变得有害,当团队为了自己的利益而追求覆盖率百分比时。

A login validator with thoughtful edge-case tests gives more value than a covered file full of trivial assertions. That’s especially true for input-heavy code such as forms, parsers, date logic, and permission checks. If your team is tightening quality around validation-heavy UI, this guide on 掌握前端表单验证 是与单元测试策略相辅相成的。

行为驱动测试能抵抗重构

一个可靠的测试套件应该让你能够重构内部逻辑而不用重写一半的测试。最简单的方法是断言 可观察行为 而不是实现细节。

能经得住时间考验的用例包括

  • 边界条件 如空输入、空值、非法类型和过长的字符串
  • 域结果 如“缺少权限时拒绝返回”
  • 状态转换 例如“下载元数据后标记更新为待处理状态”

经常会过时的使用案例

  • 检查内部辅助函数调用
  • 断言私有方法顺序
  • 模拟整个调用链中的每层

对于构建有纪律的发布流程的应用团队,Capgo的文章关于 应用质量保证 是有用的,因为它将测试工作与更广泛的发布管道联系起来

测试CI、Capacitor和Electron应用

一个只在一个开发者的机器上运行的测试并不是一个安全网。它是一个局部习惯。

CI将单元测试JavaScript工作转换为团队基础设施。每次推送、拉取请求或发布分支都可以使用相同的命令和相同的期望来执行相同的命令。这种一致性对于Capacitor和Electron项目来说尤其重要,因为环境漂移会导致微小的故障

让 CI 成为默认的执行路径

至少,您的 CI 应该在每次更改时安装依赖项并运行单元测试套件。尽可能保持命令与本地开发一致。

一个基本的 GitHub Actions 工作流程可以如此简单:

name: test

on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

这足以捕捉到导入错误、失败的断言和意外的平台假设,避免它们进入主分支。

对于通过自动化管道交付的移动团队,Capgo 提供了一个实用的指南来 为 Capacitor 应用设置 CI/CD.

测试 Capacitor 插件交互

错误的方式是通过将本地插件直接拉入每个服务来单元测试 Capacitor code。这会将测试套件耦合到平台桥接上。

更好的模式是使用薄的抽象:

// deviceStorage.js
async function saveFile(filesystem, path, data) {
  return filesystem.writeFile({ path, data });
}

module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
  await storage.save('draft.json', JSON.stringify(draft));
  return { saved: true };
}

module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');

test('persists a serialized draft', async () => {
  const storage = {
    save: jest.fn().mockResolvedValue(undefined)
  };

  const result = await persistDraft(storage, { title: 'Hello' });

  expect(result).toEqual({ saved: true });
});

同样的想法也适用于相机访问、生物识别提示、推送令牌注册和网络状态。将插件调用放在适配器中。测试应用逻辑对您控制的接口。

测试 Electron 主渲染器和 IPC code

Electron 应用有两个重要的接口: main process coderenderer process code. 在测试中不要使它们模糊.

一个可靠的设置通常将它们分开:

  • 渲染器单元测试 用于视图模型、状态、格式化和 UI 端业务逻辑
  • 主进程单元测试 用于菜单、文件操作和应用生命周期决策
  • IPC 契约测试 用于消息形状和预期响应

示例 IPC wrapper:

// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
  ipcRenderer.send('settings:update', payload);
}

module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');

test('sends settings update over ipc', () => {
  const ipcRenderer = { send: jest.fn() };

  sendSettings(ipcRenderer, { theme: 'dark' });

  expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});

If you later change the internal implementation from one helper to another, this test still holds because it verifies the behavior that matters. That’s the standard you want across desktop and mobile code.

关于 JavaScript 单元测试的常见问题

什么是单元测试、集成测试和 E2E 测试之间的区别

A 单元测试 检查一个小的逻辑单元的隔离情况。一个 集成测试 检查几个组件或服务是否能正确地协同工作。一个 端到端测试 模拟用户在运行的应用程序中进行的旅程。

使用单元测试来快速获得业务规则的信心。使用集成测试来检查 seams 如存储、插件包装器和 IPC。使用 E2E 测试谨慎地检查那些如果它们发生故障会严重影响的工作流。

我们应该追求全面覆盖

No. 全面覆盖可能会将团队推向低价值测试。

覆盖率在揭示没有被执行的风险code时有用。它并不是在工程师为了满足仪表板而添加浅表断言时有用的。如果您的测试套件脆弱,更多的覆盖率也不会拯救它。

如何在现有代码库中添加测试

从变化已经发生的地方开始。不要冻结团队并宣布巨大的重写测试策略。

实用的顺序如下:

  • 首先保护活跃code 通过在特性工作或bug修复中添加测试到您接触的模块
  • 提取纯粹的逻辑 从难以测试的文件中提取业务规则可以在框架或运行时噪音中测试
  • 添加接口包装 在原生插件、网络客户端、文件系统调用和Electron IPC周围
  • 拒绝脆弱的模式 当介绍 mock 时。来自 JavaScript 测试最佳实践的指导 尤其在这里有用,因为它突出了人们经常忽视的过度 mock 的问题以及随后的脆弱测试

目标不是立即完成。它是团队在最昂贵的回归中取得稳步进步的地方


如果您的团队发布 CapacitorElectron 应用和需要更干净的 JavaScript 变更的发布过程 Capgo 是值得考虑的一个选项。它为 CapacitorJS 和 Electron 应用提供实时更新,具有滚动控制和可观察性,因此团队可以将坚实的单元测试与不必等待每个修复在商店审查中发布的更安全的路径配对起来,

实时更新 Capacitor 应用

当 web 层 bug 活跃时,通过 Capgo 将修复推送到用户,而不是等待几天的 app store 审批。用户在后台接收更新,而本机更改保持在正常的审批路径中。

立即开始

博客最新文章

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。