跳过主要内容

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

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

马丁·多纳迪厄

马丁·多纳迪厄

内容营销

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

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

那会变得更糟 CapacitorElectron 应用程序。一个简单的功能可以触及共享的业务逻辑、浏览器API、原生插件、本地文件、IPC和远程服务在同一流程中。如果您以错误的方式测试这些部分,测试套件就变成了迷宫中的假依赖。如果您以正确的方式测试它们,您会获得对逻辑的快速反馈,逻辑会破裂。

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

目录

选择 JavaScript 测试框架

专业的 JavaScript 项目需要一个真正的测试运行器。

不成系统的脚本和手动控制台检查在多个工程师处理同一代码库时无法扩展。 您需要测试发现、断言、异步处理、模拟和在本地开发和 CI 中一致地运行所有内容的方式。 当前指导方针正在趋向于小组的主流选项。 Jest、Mocha 和 Jasmine 被反复强调为主要框架,Jest 经常被单独提及的内置测试结构、断言、模拟和异步支持的包装,正如本文所示 Pluralsight JavaScript 测试实验室.

JavaScript 测试框架的比较图表,包括 Jest、Mocha、Cypress 和 Playwright

为什么框架不是可选项

团队的第一个错误是将单元测试视为一项次要活动。这通常导致文件命名不一致、 nobody 记得的自定义断言和只有一个人理解的助手

框架给你一个共享的语言:

  • 测试结构describetest 断言 it
  • 有可读的匹配器 with
  • 钩子 用于设置和清除
  • 异步支持 用于承诺和定时器
  • 模拟工具 用于外部依赖

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

Jest vs Mocha 一瞥

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

Jest 是全能的选择。它在第一天就带来了大部分团队需要的内容
Mocha 更具模块化。它为您提供了一个运行器,并希望您自己组装整个堆栈。

特性 Jest Mocha
设置复杂度 对于大多数团队来说较低 因为您通常会添加断言和模拟库
断言 内置 通常与另一个库配对
模拟 内置 通常与另一个库一起使用
异步测试 内置且直接 支持,但依赖于周围的设置
覆盖工作流 通常与相同的工具链集成 经常是拼凑起来的
最佳匹配 新项目,希望保持一致性的团队 遗留堆栈,希望有模块化控制的团队

实用规则: 如果您的团队需要询问哪个断言库和模拟库与运行器配对,很可能您想要 Jest。

我推荐的大多数团队

对于大多数现代项目,我会选择 Jest 除非代码库已经有强烈的理由继续使用 Mocha。这种推荐在应用程序包含 Capacitor 因为这些项目已经有足够的移动部分。减少测试工具的杂乱程度会带来快速的收益。 在较旧的 Node.js 服务或长期存活的代码库中,Mocha仍然有意义,因为其周围的生态系统已经稳定。但是,对于一名中级工程师从头开始设置一个强大的套件,Jest通常比它创造的摩擦要少。一个重要的范围说明。 Cypress 和 Playwright 是优秀的工具,但它们解决的是不同的问题。它们更适合于浏览器级别和端到端检查,而不是快速的内部循环,JavaScript 单元测试应该居住的地方。

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

live_update_platform_capacitor_title

live_update_lts_electron

一个干净的测试环境应该是无聊的。如果添加第一个测试感觉复杂,测试套件可能不会健康地存活。

A man wearing glasses working on a programming project on a laptop at a wooden desk.

一个简单的 Jest 设置

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

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

这对于许多项目来说已经足够了。如果你的模块系统、转译或多包结构需要它,你可以在后面添加更多的配置。

如果你正在本地构建一个 Capacitor 应用,并且想要在添加测试之前将开发环境保持在有序状态,Capgo 的指南 如何设置一个 Capacitor 本地环境 在 __CAPGO_KEEP_0__ 之前编写测试

Write the test before the code

在编写测试之前 writing the test first,组织测试 describeit,并在其 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. 模式使测试保持可读,即使它们变得更加复杂。 the input and any needed setup.
  2. Act by calling the function.
  3. Assert on the outcome.

Applied to a validation helper:

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);
  });
});

javascript中的单元测试

For Capacitor and Electron projects, that discipline matters more because your pure logic often sits next to native or desktop integration code. Keep the business rule testable without the platform runtime, and your first test won’t be your last useful one.

Mastering Mocks and Asynchronous Code

Most bugs in application code don’t come from adding two numbers. They come from code that reaches outside itself: network requests, files, plugin APIs, timers, IPC channels, storage layers.

That’s where mocking helps. It gives you control over the boundary so the test can focus on your code’s decision-making.

javascript中的单元测试

模拟边界,不是所有的

可维护的测试指导强调 单一行为覆盖一个强大的断言,它还警告说,过度使用模拟会使测试变得脆弱并紧密耦合到实现细节中,如本 TestRail文章关于可维护的单元测试.

这条警告在JavaScript中非常重要。团队经常从模拟每个导入的模块开始,最后会测试函数是否正确调用其他函数,而不是测试真实行为。

模拟过多测试的坏目标:

  • 助手A是否调用了助手B
  • 服务C是否调用了序列化器D
  • 内部私有函数是否运行了两次

更好的目标:

  • 函数返回的内容
  • 它是否正确处理了依赖项的失败
  • 它是否将数据转换为期望的形状

更好的模式:Capacitor和Electroncode

在移动和桌面应用中,我更喜欢在原生或平台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集成等后台服务

For teams testing release logic and update paths in Capacitor apps, Capgo has a relevant guide on 对于测试Capacitor应用发布逻辑和更新路径的团队,__CAPGO_KEEP_1__有一个相关的指南.

测试__CAPGO_KEEP_0__OTA更新的模拟场景:

测试异步流程而不引入不稳定性

使用 async/await in tests when the code under test returns a promise. It’s clearer than callback-heavy patterns and easier to debug.

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');
});

当测试的__CAPGO_KEEP_0__返回一个Promise时。它比使用回调函数的模式更清晰,更容易调试。

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');
});

也测试失败路径:

测试两条路径:成功路径和失败路径。在生产环境中,失败路径往往是用户记住的。

A test suite becomes useful when it stays useful after the code changes. That’s harder than writing a pile of passing tests.

一个测试套件只有在它保持有用性,即使__CAPGO_KEEP_0__发生变化时才是有用的。写一堆通过的测试比这更容易。

一幅图表,展示了通过全面测试覆盖和可维护的测试套件来构建健壮的软件的策略。

使用测试分离的预算 70/20/10 一项实用的指南建议将测试分为 单元测试、集成测试和端到端测试, with unit tests providing the fastest feedback and most stable failures. The same guidance says a full unit suite should ideally finish in under 10 seconds, and pre-commit checks should stay under 5 seconds, according to this OpenReplay testing guide.

I treat that as a budgeting tool, not a religion. If most of your effort goes into end-to-end tests, your team will wait too long for feedback. If everything is unit-only, you’ll miss real system boundaries.

For a Capacitor or Electron app, a healthy balance usually looks like this:

  • Unit tests for pricing logic, permissions rules, serialization, update eligibility, feature flags, and state transforms
  • Integration tests 对于 __CAPGO_KEEP_0__ 或 Electron 应用程序,健康的平衡通常如下:
  • 端到端测试 为关键旅程,如登录、购买流程、同步或更新提示,进行测试

覆盖率是一个指南针,而不是目标

覆盖率报告在帮助您发现重要逻辑中未测试的 branch 时是有用的,但当团队追求覆盖率百分比而不是质量时,它们就变得有害

一个考虑了边缘案例的登录验证器比一个包含了大量无关紧要断言的覆盖的文件更有价值。尤其是对于输入密集的code,如表单、解析器、日期逻辑和权限检查。如果您的团队正在围绕验证密集的UI提高质量,这篇关于 前端表单验证的指南 的指南是与单元测试策略相辅相成的

行为驱动测试可以抵抗重构

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

能经得住时间考验的用例:

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

常常会出现问题的用例:

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

对于构建有纪律发布流程的应用团队,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 应用程序有两个重要的缝隙: 主进程 code渲染进程 code. 不要在测试中模糊它们。

可靠的设置通常分离为:

  • 渲染器单元测试 用于视图模型、状态、格式化和 UI-side 业务逻辑
  • 主进程单元测试 对于菜单、文件操作和应用程序生命周期决策
  • 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' });
});

如果您稍后更改内部实现从一个助手到另一个助手,这个测试仍然有效,因为它验证了重要的行为。 这就是您希望在桌面和移动设备上实现的标准 code。

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

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

A 单元测试 检查一个小的逻辑单元的独立性。一个 集成测试 检查几个组件或服务是否正确地一起工作。一个 end-to-end测试 模拟用户在运行应用程序的完整用户体验。

使用单元测试快速确保业务规则。使用集成测试测试存储、插件包装器和IPC等接口。使用E2E测试仅用于可能严重影响应用程序稳定性的工作流。

我们应该追求全面覆盖

不。全面覆盖可能会导致团队优先考虑低价值测试。

覆盖率在揭示风险的code时有用,但在工程师仅为了满足仪表板而添加浅表断言时则无用。如果测试套件脆弱,增加覆盖率也不会解决问题。

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

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

一个实际的顺序如下:

  • 首先保护活跃的code 通过在特性工作或bug修复期间添加测试来保护模块
  • 提取纯粹的逻辑 从难以测试的文件中抽取业务规则,以便在不引入框架或运行时噪音的情况下测试业务规则
  • 添加接口包装 在原生插件、网络客户端、文件系统调用和 Electron IPC 周围添加包装
  • 拒绝脆弱的模式 当引入模拟时拒绝脆弱的模式。来自 JavaScript 测试最佳实践 的指导是特别有用的,因为它突出了过度模拟和随后的脆弱测试的常见问题

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


如果您的团队发布 CapacitorCapacitor JavaScript应用和需要一个更清洁的发布过程 Capgo CapacitorJS和Electron应用提供实时更新,具有回滚控制和可观察性,团队可以将坚实的单元测试与更安全的路径结合起来,用于在不等待每个修复的商店审核的情况下发布Web包更改

Capacitor应用的实时更新

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

当web层bug出现时,通过__CAPGO_KEEP_0__将修复推送给用户,而不是等待几天的app store审批。用户在后台接收更新,而native变化仍在正常的审批路径中。背景:Capgo营销网站。角色:支持描述段落或元描述。见于:组件GetStarted.astro。保留Capgo产品/品牌和开发者术语的原始形式。消息键`instant_updates_for_capacitor_apps_description` (Capacitor应用的实时更新描述)。

立即开始

最新博客

Capgo gives you the best insights you need to create a truly professional mobile app.