一、开始测试
这个动手模块带你学习完整的 Lightning Web Components 测试工作流——用 Salesforce 推荐的 JavaScript 测试框架 Jest。你将理解为什么测试重要(单元测试 vs 端到端测试)、用 sfdx-lwc-jest 搭建 Jest 测试框架、为 LWC 组件编写基本测试、测试 @wire 服务适配器(通用/LDS/Apex),以及 mock 基础组件和其他组件。前置要求:熟悉 LWC、Salesforce DX 与 Visual Studio Code。
为什么测试很重要
「设计阶段没发现的 bug,到了编码阶段要多花十倍时间才能发现,到了调试阶段再多花十倍。」——Dr. Nikolai Bezroukov《The Art of Debugging》。翻译过来就是:尽早发现 bug。bug 检测成本随阶段递增:设计阶段 1 倍(最便宜)、编码阶段 10 倍、调试阶段 100 倍、生产环境则代价惨重。
调试与测试是相关但不同的过程:测试试图发现并报告错误;调试试图识别原因并修复。测试是你的第一道防线——它防止 bug 到达用户。
测试心态:测试不是证明代码「能用」,而是找出它「哪里会失败」。好的测试覆盖:正常情况(典型用户场景)、边界情况(空状态、最大值)、错误情况(网络失败、坏数据)。在 LWC 测试中,要测:组件渲染(是否显示正确内容)、组件行为(按钮点击、输入变化是否正确)、数据流(@wire 是否返回预期数据、属性是否响应式)、事件(是否派发正确事件)。测试是一项技能,不是苦差事。
单元测试
单元测试(unit test)专注于测试应用中小的、离散的功能片段——独立测试单个函数、组件或方法。好的单元测试:快(毫秒级)、隔离(无网络/数据库/外部依赖)、可重复、自校验、及时(随代码编写,理想情况下通过 TDD 先写)。
对 LWC,一个单元测试通常:隔离测试一个组件、mock 所有外部依赖(@wire、LDS、Apex、子组件)、断言渲染后的 DOM、属性值与派发的事件。Jest 就是 LWC 的单元测试框架——它提供 describe/it/expect 测试基础设施,sfdx-lwc-jest 添加 LWC 特定层(解析 LWC 模块导入、模拟 Salesforce 服务、提供基础组件 stub)。基本模式:import { createElement } from 'lwc' → 创建元素 → 追加到 DOM → 查询 shadow DOM → 断言。
端到端测试
端到端测试(E2E)测试整个应用流程,像真实用户那样体验。特点:针对真实组织(或真实测试环境)运行、测试多个组件协同工作、模拟真实用户交互(登录、导航、填表、点击)、比单元测试慢(秒级而非毫秒级)、更复杂。E2E 测试回答:用户能否登录并创建记录?搜索页是否返回正确结果?结账流程能否从头走到尾?
常见的 Salesforce E2E 测试工具:Selenium WebDriver(浏览器自动化)、WebDriverIO(现代 Selenium 封装)、UTAM(Salesforce 特定的页面对象模型)。E2E 的权衡:优点是最高的信心(测真实用户体验、捕获单元测试漏掉的集成问题);缺点是慢、脆弱(UI 改动会破坏测试)、设置复杂。本模块聚焦 Jest 单元测试——更快、更可靠、是任何测试策略的基础。
单元测试 vs 端到端测试
两者并排对比:范围——单元测试是单个组件,E2E 是整个应用;速度——毫秒 vs 秒/分钟;依赖——全部 mock vs 真实组织;可靠性——高 vs 中;维护——低 vs 高;信心——组件级 vs 系统级。两者都有价值:单元测试回答「这个组件能工作吗?」E2E 回答「整个应用能工作吗?」好的测试策略两者都用。
测试金字塔是行业标准:底部(最多)是单元测试——每个组件都要有,快而可靠;中部是集成测试——组件间交互;顶部(最少)是E2E 测试——关键业务流程。本模块教的是金字塔的底层——一切的基础。掌握单元测试,你就掌握了最重要的测试技能。
二、搭建 Jest 测试框架
本单元创建 SFDX 项目、学习 Node.js 与 npm(JavaScript 运行时与包管理器)、安装 Jest 与 sfdx-lwc-jest、为自动化测试配置项目。最终你会有一个完整的测试环境,能从命令行和 VS Code 内运行 Jest 测试。
创建 SFDX 项目
Jest 单元测试的一大关键优势:测试完全在你本地的 Node.js 上运行——无需 Salesforce 组织连接、无需部署、无需 API 调用。测试毫秒级运行、可离线、适合 CI/CD、无组织限制。SFDX 项目提供组件源代码,Jest 用 sfdx-lwc-jest 的模拟 LWC 环境对源码执行测试。
创建项目:VS Code 里 Ctrl+Shift+P → SFDX: Create Project → Standard → 命名(如 test-lwc)→ 选位置。测试放在每个组件旁的 __tests__ 文件夹里(如 lwc/myComponent/__tests__/myComponent.test.js)。这种同址(co-location)是标准模式——测试紧挨被测代码。双下划线前缀与 .test.js 扩展名是 Jest 约定,Jest 自动发现并运行,无需配置。完整工作流:创建组件 → 创建 __tests__ 文件夹 → 写 .test.js → npm test。
Node.js 与 npm
Node.js = 运行在浏览器之外的 JavaScript 运行时。为什么对 LWC 测试重要:Jest 是 Node.js 应用(用 Node.js 而非浏览器运行测试);sfdx-lwc-jest 在 Node.js 中模拟浏览器 API(DOM、自定义元素)——这就是测试无需浏览器或组织的原因。npm = Node 包管理器:安装 JavaScript 库(Jest、sfdx-lwc-jest)、在 package.json 管理项目依赖、运行脚本(如 npm test)。
package.json 是 Node.js 项目的清单文件:scripts 定义可用 npm run <name> 运行的命令;devDependencies 放开发/测试所需库(不部署到生产,如 Jest、sfdx-lwc-jest);dependencies 放运行时库。npm test 运行 test 脚本即运行 Jest——一条命令运行全部测试。
安装:从 nodejs.org 下载 LTS 版(npm 随 Node.js 一起安装),用 node --version、npm --version 验证。在项目里 npm init -y 创建 package.json,npm install --save-dev @salesforce/sfdx-lwc-jest jest 安装测试依赖。
Jest:测试运行器 + 断言库
Jest 是 Meta(Facebook)维护的 JavaScript 测试框架,All-in-one:测试运行器(发现并执行测试)、断言库(expect().toBe())、mock 框架(jest.fn()、mock 模块与函数)、代码覆盖率(报告测试覆盖百分比)。Salesforce 选择 Jest 作为 LWC 的测试框架,因为它与 Web Components 标准对齐。
核心概念(学会这 5 个就能写任何 Jest 测试):
- describe:分组测试(可嵌套)。
- it(或 test):单个测试用例。
- expect:断言(检查值)。
- 匹配器(matchers):
.toBe()、.toEqual()、.toContain()、.toThrow()、.toBeNull()、.toHaveLength()等。 - setup/teardown 钩子:
beforeEach、afterEach(每个测试前后)、beforeAll(所有测试前一次)。
安装 sfdx-lwc-jest 并运行测试
sfdx-lwc-jest = Salesforce 为 LWC 测试提供的 Jest 配置,它提供:Jest 配置(教会 Jest 解析 lwc、lightning/*、@salesforce/* 模块导入)、LWC 环境模拟(Shadow DOM、自定义元素 API、模板渲染)、Salesforce 服务 mock(LDS getRecord、Apex wire 适配器、Lightning Message Service)、基础组件 stub(lightning-input、lightning-button 等所有 lightning-* 命名空间组件)。一次 npm install,完整的 LWC 测试环境。
配置与运行:npm install --save-dev @salesforce/sfdx-lwc-jest → 在 package.json 的 scripts 加 "test": "jest" → npm test。Jest 自动发现 __tests__ 文件夹里所有 .test.js 文件。结果显示每个测试套件的 PASS/FAIL、通过/失败数、执行时间(可选覆盖率)。绿色对勾 = 通过,红色 X = 失败并显示预期 vs 实际值。
常用命令:npm test(运行全部一次)、npm test -- --watch(监视模式,文件变化自动重跑,TDD 理想)、npm test -- --coverage(代码覆盖率报告)。
用 package.json 自动化 + VS Code 点击运行
package.json scripts 自动化测试工作流:定义 "test": "jest"、"test:watch": "jest --watch"、"test:coverage": "jest --coverage",用 npm run test:watch 等运行。好处:跨机器一致、无需记忆 Jest CLI 参数、CI/CD 用同一脚本、新人只需 npm test。脚本成为项目测试工作流的标准接口。
这也让项目CI/CD 就绪:CI 流水线只跑 npm ci(干净安装依赖)+ npm test(运行全部测试)。任何测试失败 → 构建失败 → 代码不部署。这就是持续集成:每次代码变更 → 自动化测试 → 通过才合并/部署。设置一次,未来每次 push 都受益。
VS Code 点击运行:安装 Jest 扩展后可获得内联测试状态(绿色/红色指示器)、点击运行单个测试、断点调试测试、Problems 面板显示结果。调试测试:设断点 → F5 → 选 Jest 调试器 → 逐步执行(F10 step over、F11 step into),检查变量、调用栈,理解测试为何失败。
三、编写 Jest 测试
本单元动手写测试。你从一个真实的 Lightning web component 开始,为它写基本 Jest 测试、运行测试、处理异步 DOM 更新(数据加载后组件渲染的情况),并测试生命周期钩子。
从一个组件开始并编写基本测试
创建一个简单的组件来测试(如 helloWorld:显示问候语、用 lightning-input 接受用户输入、输入变化时响应式更新)。每个 LWC Jest 测试都遵循这个 setup 模式:import { createElement } from 'lwc' + import HelloWorld from 'c/helloWorld' → describe 分组 → afterEach 清理 DOM → it 测试用例里 createElement('c-hello-world', { is: HelloWorld }) → document.body.appendChild(element) → 查询 shadow DOM → 断言。createElement → append → query → assert,这个模式永远不变。
基本渲染测试验证组件默认状态:组件无错渲染、默认 @track 值被使用、模板绑定生效、内容出现在正确 DOM 位置。这能捕获模板语法错误、属性未初始化、绑定错误、DOM 结构变化。测试用户交互则模拟用户输入:找到 lightning-input、设置 value、dispatchEvent(new CustomEvent('change'))、然后断言问候语更新——测试完整的「输入 → 事件 → 处理器 → 状态变化 → 重渲染 → DOM 更新」链路。
运行测试与 TDD
运行 npm test 得到清晰可读的报告:每个套件显示 PASS/FAIL,每个用例显示对勾/叉与耗时。失败测试显示确切的断言、预期值与实际值——无需猜测、无需 console.log。这种即时精确的反馈让单元测试成为高效开发实践。
TDD(测试驱动开发)循环:1) RED——写一个失败测试(先定义还没实现的行为);2) GREEN——写最少代码让测试通过;3) REFACTOR——清理代码同时保持测试通过。对 LWC 用 npm test -- --watch:保存文件 → Jest 自动重跑受影响测试 → 一秒内看结果 → 修复 → 保存 → 重复。这个快速反馈循环很「上瘾」,把测试从苦差变成高效开发节奏。
测试异步 DOM 更新
LWC 异步渲染——状态变化不会立即更新 DOM。因此测试必须等待渲染完成。错误做法:同步断言(看到的是旧 DOM)。正确做法:返回 Promise——return Promise.resolve().then(() => { expect(...).toBe('new value') })。Promise.resolve() 把断言调度到当前微任务队列之后,正是 LWC 执行 DOM 更新的地方。
现代 async/await 语法更清晰:it('updates text', async () => { element.property = 'new value'; await Promise.resolve(); expect(...).toBe('new value'); })。经验法则:改状态 → await Promise.resolve() → 断言更新后的 DOM。每次改 tracked 属性都插一个 await,这是廉价的保险。
测试生命周期钩子
测试生命周期钩子验证组件的初始化与清理逻辑正确运行。用 jest.fn() 创建mock 函数(记录调用次数与参数),替换组件原型的生命周期方法:HelloWorld.prototype.connectedCallback = callback; 然后创建组件、断言 expect(callback).toHaveBeenCalled()。这个模式可测试任何生命周期钩子:connectedCallback、renderedCallback、disconnectedCallback、errorCallback。
disconnectedCallback 清理测试:把钩子替换为 spy → append 后立即 removeChild → 断言清理 spy 被调用。这对管理外部资源的组件至关重要——如果组件在 connectedCallback 订阅 LMS 消息却不在 disconnectedCallback 取消订阅,就会内存泄漏和幽灵事件处理器。这个简单测试能防止一整类与组件生命周期管理相关的生产 bug。
四、为 Wire Service 编写 Jest 测试
本单元测试使用 @wire 访问 Salesforce 数据的组件——三种 wire 适配器:通用适配器、Lightning Data Service(getRecord)、Apex 方法。因为 Jest 测试没有 Salesforce 组织,你需要提供mock 数据来模拟服务器返回。这是真实 LWC 组件最重要的测试技能,因为大多数生产组件都通过 @wire 拉取数据。
测试 @Wire 服务:mock 模式与三种适配器
@wire 连接组件与 Salesforce 数据,但 Jest 没有组织——解决方案:提供模拟服务器返回的mock 数据。sfdx-lwc-jest 提供注册 mock wire 适配器的工具。模式:导入组件 → 创建匹配真实服务器返回结构的 mock 数据 → 注册为 wire 响应 → 创建组件 → 断言渲染输出匹配 mock 数据。
LWC 支持三种 @wire 适配器,mock 方式略有不同:
- 通用 wire 适配器:自定义 @wire 函数,用
jest.mock()或手动 emit 数据。 - LDS(getRecord):从
lightning/uiRecordApi导入,sfdx-lwc-jest 提供registerLdsTestWireAdapter。 - Apex wire 适配器:从
@salesforce/apex/...导入,sfdx-lwc-jest 提供registerApexTestWireAdapter。
断言模式对三种都一样:await → 查询 shadow DOM → expect。测试 wire 时要覆盖三个状态:加载(data null)、成功(data 有值)、错误(error 有值)——不要只测快乐路径,加载态和错误处理才是真正藏 bug 的地方。
通用 wire 适配器
通用 wire 适配器最灵活,因为一切由你控制。常见做法:创建匹配组件预期结构的数据,用 registerWireService 注入。关键细节:mock 数据结构必须与真实 wire 适配器返回的结构匹配——如果组件访问 data.name,你的 mock 就要有含 name 子属性的 data 属性。数据结构不匹配是 wire 测试失败最常见的原因。
wire 适配器有三个应测试的状态:LOADING(data undefined、error undefined → 断言显示 spinner/占位文本)、SUCCESS(data 有值 → 断言显示正确数据)、ERROR(error 有值 → 断言显示错误消息)。用 registerWireService(element, { data: ..., error: ... }) 注入不同状态。只测成功状态是最常见的测试缺口——真实组件会在加载态停留、会遇到错误,每个状态都有不同 UI 且都应测试。
LDS wire 适配器(getRecord)
测试用 @wire(getRecord, {...}) 的 LDS 组件:用 sfdx-lwc-jest 的 registerLdsTestWireAdapter(getRecord) 创建 mock 适配器,调用 .emit(mockRecordData) 模拟数据到达。关键细节:mock 数据必须匹配 LDS 记录结构——字段包裹在 { value: ... } 里(如 fields: { Name: { value: 'Acme Corp' } })。
测试 LDS 错误状态:getRecordMock.error('Record not found') 或 emit 特定错误结构。验证组件:显示错误消息、隐藏加载 spinner、提供重试选项、区分网络错误与数据错误。registerLdsTestWireAdapter 也适用于其他 LDS 适配器(getRecord、getObjectInfo、getPicklistValues)——模式相同,只是 mock 数据结构不同。获取测试数据的最好方式是用 REST 客户端访问 User Interface API 抓取数据快照,比手写 JSON 更准确。
Apex wire 适配器
测试用 @wire 加导入 Apex 方法的组件:用 registerApexTestWireAdapter(getAccounts) 创建 mock,.emit([{ Id: '1', Name: 'Acme' }, ...]) 模拟服务器返回。与 LDS 不同,Apex wire 返回普通 JavaScript 对象(无 { value: ... } 包裹)——mock 数据结构取决于你的 Apex 方法返回类型,要精确匹配。
完整 Apex wire 测试模式:创建 mock 适配器 → 创建组件 → emit mock 数据 → await Promise.resolve() → 查询 shadow DOM 断言。关键洞察:你通过 .emit() 调用控制数据何时到达——可立即 emit(模拟快速响应)、延迟 emit(测试加载态)、或 emit 错误(模拟服务器失败)。注意测试多个 mock 时在 afterEach 里用 jest.clearAllMocks() 防止数据在测试间泄漏。被 @wire 调用的 Apex 方法必须标注 @AuraEnabled(cacheable=true)。
五、Mock 其他组件
最后一个单元。当你的组件包含其他组件(基础 Lightning 组件或自定义子组件)时,单元测试需要 mock 它们。sfdx-lwc-jest 为所有基础组件提供内置 stub;自定义组件则自己创建 mock stub。本单元两者都覆盖,确保通过 mock 一切外部依赖让组件测试真正隔离。
Mock 基础组件
sfdx-lwc-jest 为所有基础 Lightning 组件提供预构建的 stub:lightning-button、lightning-input、lightning-datatable、lightning-card、lightning-toast 等所有 lightning-* 组件。Stub 是简化版:接受与真实组件相同的 @api 属性、派发标准事件(change、click)、渲染最小 DOM(够查询)、不含完整样式与行为。
这让你能测试自己的组件传递了正确的属性给基础组件,而无需真实实现。测试模式:查询 shadow DOM 里的 stub(如 lightning-input)、验证属性(expect(input.label).toBe('Name')、expect(input.value).toBe('World'));测试事件则派发事件(input.dispatchEvent(new CustomEvent('change')))并验证组件处理器响应。Stub 由 Salesforce 维护、随新基础组件版本同步更新。这就是单元测试的本质:把你的代码从依赖中隔离出来。
Mock 其他组件
当组件包含自定义子组件时,为它们创建 stub:
- 方法一(jest.mock):
jest.mock('c/childComponent', () => ({ default: class extends LightningElement { @api recordId; } }), { virtual: true })——把子组件导入替换为实现公共 API 的最小类。 - 方法二(stub 文件):在测试 stub 目录创建最小版本,接受相同 @api 属性、派发标准事件。
Stub 应实现子组件的公共 API(@api 属性、事件),不需要子组件内部逻辑——你测的是父组件,不是子组件。
完整的 LWC 测试工具包:1) 基本测试(渲染默认态、用户交互、生命周期钩子);2) @wire 测试(成功/加载/错误三态,通用/LDS/Apex 适配器);3) 组合测试(父组件传正确 @api 属性、处理子事件,子组件用 stub mock);4) 边界用例(空/null 数据、非法输入、缺失必需 @api 属性)。把这四类应用到每个组件,你就能在 bug 到达用户之前捕获它们——这就是专业的 LWC 开发。
























































