Angular 单元测试实践指南:Vitest 默认测试体系、配置详解与 CI 集成
单元测试是保障 Angular 应用质量的第一道防线,它能尽早捕获回归、约束代码行为并为安全重构提供信心。本指南以 Angular 官方文档站点(本仓库 adev 目录)中 单元测试概览 为核心,系统讲解基于 Vitest 的默认测试环境如何搭建、如何通过 angular.json 精确控制测试行为、如何在真实浏览器中运行用例以及如何将测试接入持续集成流程。阅读完成后,你将能够独立配置、运行、调优 Angular 项目的单元测试流水线,并理解其底层工作机制。
为什么 Angular 默认选择 Vitest
测试 Angular 应用能够帮助你确认它按照预期工作。单元测试(Unit tests)对于尽早发现 bug、保证代码质量以及支持安全的代码重构都至关重要。
对于新建的 Angular CLI 项目,CLI 会默认安装并配置好使用 Vitest 测试框架所需的一切:新项目默认自带 vitest 与 jsdom 两个依赖。
说明:本指南描述的是 Angular CLI 新项目的默认测试配置(Vitest 方案)。如果你正从 Karma 迁移既有项目,请阅读 从 Karma 迁移到 Vitest 指南。Karma 依然受支持,相关内容见 Karma 测试指南。
从实现原理看,Vitest 在 Node.js 环境中运行你的单元测试,为模拟浏览器 DOM 使用名为 jsdom 的库。这样可以避免启动真实浏览器的开销,从而大幅加快测试执行速度。你可以自行替换 DOM 模拟库:安装 happy-dom 并卸载 jsdom 即可切换。目前 jsdom 与 happy-dom 是受支持的两种 DOM 模拟库(这一点与迁移文档中的说明一致——CLI 检测到已安装的 happy-dom 时会优先使用它,否则回退到 jsdom)。
快速开始:运行你的第一个测试
用 CLI 创建的项目会立即具备可测试能力。运行 ng test 命令:
ng test
ng test 会以 watch 模式(监听模式) 构建应用并启动 Vitest 测试运行器。控制台输出大致如下:
✓ src/app/app.spec.ts (3)
✓ AppComponent should create the app
✓ AppComponent should have as title 'my-app'
✓ AppComponent should render title
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 18:18:01
Duration 2.46s (transform 615ms, setup 2ms, collect 2.21s, tests 5ms)
这份输出清晰展示了 Vitest 的运行结构:按测试文件聚合用例,报告通过的测试文件数与用例总数,并给出耗时分布(transform 编译、setup 初始化、collect 收集、tests 执行各阶段耗时)。
ng test 同时会监听文件变化——一旦你修改并保存了源文件或测试文件,测试将自动重新执行。这种 watch 反馈循环正是本地开发时保持代码健康的高效方式。
配置测试行为:angular.json 的 test 目标
Angular CLI 替你处理了绝大部分 Vitest 配置。你可以通过修改 angular.json 中项目的 test 目标(architect.test)选项来定制测试行为,其使用的构建器为 @angular/build:unit-test。
Angular.json 可用选项
以下选项是默认测试配置中可直接控制的核心参数:
| 选项 | 说明 | 默认值 |
|---|---|---|
include |
需要纳入测试的文件 glob 模式 | ['**/*.spec.ts', '**/*.test.ts'] |
exclude |
需要从测试中排除的文件 glob 模式 | — |
setupFiles |
全局 setup 文件路径列表(如 polyfill 或全局 mock),会在测试执行前加载 | — |
providersFile |
导出默认 Angular providers 数组的文件路径,用于向测试环境注入全局测试 providers | — |
coverage |
是否开启代码覆盖率报告 | false |
browsers |
在真实浏览器中运行的浏览器名称数组(如 ["chromium"]),需要安装对应的 browser provider |
— |
其中 include 采用 glob 匹配,意味着只要命名符合 *.spec.ts 或 *.test.ts 的测试文件都会被自动发现;exclude 与之相反,用于剔除如生成文件、E2E 辅助代码等不应参与单元测试的文件。browsers 的详细用法见下文"在浏览器中运行测试"一节。
全局测试设置文件与全局 providers
setupFiles 与 providersFile 两个选项对管理全局测试配置尤其有用。
例如,你可以创建一个 src/test-providers.ts 文件,为所有测试统一提供 provideHttpClientTesting:
import {EnvironmentProviders, Provider} from '@angular/core';
import {provideHttpClientTesting} from '@angular/common/http/testing';
const testProviders: (Provider | EnvironmentProviders)[] = [provideHttpClientTesting()];
export default testProviders;
然后在 angular.json 中引用该文件:
{
"projects": {
"your-project-name": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"options": {
"providersFile": "src/test-providers.ts"
}
}
}
}
}
}
这里不妨深入到源码层面,看看 provideHttpClientTesting() 究竟做了什么。在本仓库的 provider.ts 中可以看到其完整实现:
export function provideHttpClientTesting(): Provider[] {
return [
HttpClientTestingBackend,
{provide: HttpBackend, useExisting: HttpClientTestingBackend},
{provide: HttpTestingController, useExisting: HttpClientTestingBackend},
{provide: ɵREQUESTS_CONTRIBUTE_TO_STABILITY, useValue: false},
];
}
它把 HttpBackend(HttpClient 真正发起请求的后端)与 HttpTestingController(测试控制器)都指向同一个 HttpClientTestingBackend 实现,并声明测试请求不参与应用稳定性判断。这样,所有使用 HttpClient 发起的请求在测试中都会被这个 mock 后端拦截,由 HttpTestingController(提供 expectOne、match、flush 等断言与冲刷 API)接管,而不会真正发出网络请求。通过 providersFile 全局注入后,所有测试模块都能自动获得这套 HTTP mock 能力,无需在每个测试文件里重复配置。
提示:当新建
src/test-providers.ts这类 TypeScript setup 文件时,请确保它被包含在项目的测试 TypeScript 配置(通常是tsconfig.spec.json)中,这样 TypeScript 编译器才能在测试期间正确处理这些文件。
高级 Vitest 配置:自定义 runnerConfig
对于高级用例,你可以在 angular.json 中通过 runnerConfig 选项提供一个自定义的 Vitest 配置文件。
注意:虽然自定义配置可以解锁更多高级选项,但 Angular 团队不对此配置文件的具体内容或其中使用的第三方插件提供支持。同时 CLI 会覆盖部分属性(
test.projects、test.include)以保证正确集成——也就是说,这两个属性的最终值仍以 CLI 计算为准。
创建一个 Vitest 配置文件(如 vitest-base.config.ts)并在 angular.json 中引用它:
{
"projects": {
"your-project-name": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest-base.config.ts"
}
}
}
}
}
}
更省事的做法是用 CLI 直接生成基础配置文件:
ng generate config vitest
这会创建一个可自由定制的 vitest-base.config.ts。迁移指南中还提到,把 runnerConfig 设为 true 时,构建器会自动在工作区根目录查找共享的 vitest-base.config.* 文件,适合多项目复用同一份基础配置。更完整的 Vitest 配置选项请参阅官方 Vitest 文档。
代码覆盖率报告
在 ng test 命令后追加 --coverage 标志即可生成代码覆盖率报告:
ng test --coverage
报告会生成在项目的 coverage/ 目录中,打开其中的 index.html 即可查看带源码标注与覆盖率的可视化报告。
在 Angular CLI 中,代码覆盖率是一等公民能力:ng test --coverage 即可开启。详细配置(如覆盖率阈值、统计口径)请参阅 代码覆盖率指南。简单来说,你可以在 angular.json 中通过以下选项进一步控制覆盖率行为:
coverage:布尔值,设为true后每次测试都自动产出报告;coverageThresholds:设定statements、branches、functions、lines的最低覆盖率百分比,低于阈值时测试命令直接失败,从而在 CI 中强制执行覆盖率门槛;coverageInclude/coverageExclude:控制哪些文件计入覆盖率统计;coverageReporters:报告格式数组(如html、lcov、json);coverageWatermarks:为 HTML 报告设置[low, high]色阶阈值,影响报告的颜色编码。
使用前提:Vitest 的覆盖率功能需要额外安装 @vitest/coverage-v8 包,请使用 npm install --save-dev @vitest/coverage-v8(或 yarn / pnpm / bun 的对应命令)预先安装。
在真实浏览器中运行测试
大多数单元测试在默认的 Node.js 环境(配合 jsdom DOM 模拟)中执行会更快。但某些依赖浏览器专属 API(如真实渲染能力)或用例需要调试场景时,你也可以让测试跑在真实浏览器里。
第一步是安装一个浏览器 provider(browser provider)。Vitest 浏览器模式的更多机制可参阅其官方文档。安装完成后,通过 angular.json 的 browsers 选项或 --browsers CLI 标志即可在浏览器中运行测试:
{
"projects": {
"your-project-name": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"options": {
"browsers": ["chromium"]
}
}
}
}
}
}
浏览器名称与所装的 provider 相关(如 Playwright 用 chromium、WebdriverIO 用 chrome)。命令行方式示例如下:
# 示例:Playwright(有头模式 headed)
ng test --browsers=chromium
# 示例:Playwright(无头模式 headless)
ng test --browsers=chromiumHeadless
# 示例:WebdriverIO(有头模式)
ng test --browsers=chrome
# 示例:WebdriverIO(无头模式)
ng test --browsers=chromeHeadless
有头/无头模式的判定规则:默认情况下测试以有头模式运行;一旦环境变量 CI 被设置,则自动切换为无头模式;若想显式控制,可以在浏览器名后追加 Headless 后缀(如 chromiumHeadless、chromeHeadless)。
当前可按需选择三类浏览器 provider:
Playwright
Playwright 是支持 Chromium、Firefox 与 WebKit 的浏览器自动化库。安装命令:
npm install --save-dev @vitest/browser-playwright playwright
(使用 yarn 时对应 yarn add --dev @vitest/browser-playwright playwright;使用 pnpm 时对应 pnpm add -D @vitest/browser-playwright playwright;使用 bun 时对应 bun add --dev @vitest/browser-playwright playwright。)
WebdriverIO
WebdriverIO 是支持 Chrome、Firefox、Safari 与 Edge 的浏览器/移动端自动化测试框架。安装命令:
npm install --save-dev @vitest/browser-webdriverio webdriverio
(yarn / pnpm / bun 的对应安装命令同理,将包管理器命令替换即可。)
Preview
@vitest/browser-preview 面向 WebContainer 环境(如 StackBlitz)设计,不适用于 CI/CD 场景。安装命令:
npm install --save-dev @vitest/browser-preview
提示:更精细的浏览器相关配置(如自定义服务器、浏览器启动参数等)可回到上文"高级 Vitest 配置"一节,通过
runnerConfig自定义 Vitest 配置实现。
其他测试框架
你也可以使用其他测试库与测试运行器来对 Angular 应用做单元测试。每种库/运行器都有各自的安装流程、配置方式与测试语法,本文所述默认配置仅覆盖 Vitest 方案;如需 Jamine+Karma 等传统组合,可参考 Karma 测试指南,其中详细介绍了 Karma 的配置与启动方式。
在持续集成(CI)中运行测试
一套健壮的测试套件是持续集成流水线的关键组成部分——CI 服务器让你能够在每次提交与每个 Pull Request 上自动运行测试。
在 CI 服务器中测试 Angular 应用,只需运行标准测试命令:
ng test
绝大多数 CI 服务器会设置 CI=true 环境变量,ng test 检测到后会自动将测试配置为非交互式的单次运行模式(single-run),执行完毕即退出。
如果你的 CI 服务器没有设置该变量,或需要手动强制单次运行,可以加上 --no-watch 与 --no-progress 标志:
ng test --no-watch --no-progress
--no-watch 关闭监听让测试进程在执行完后退出(避免 CI 挂起),--no-progress 则禁用交互式进度显示,使输出更适合流水线日志归档。此外,前文提到 CI 环境下浏览器测试也会自动切换为无头模式,可保证其在无显示器的构建机(如 Jenkins、GitHub Actions runner)上正常运行。
与既有项目的衔接:从 Karma 迁移到 Vitest
如果你的项目仍然使用 Karma + Jasmine,官方提供了一条经过验证的迁移路径,详见 从 Karma 迁移到 Vitest 指南。要点如下:
- 手动步骤:安装
vitest与jsdom/happy-dom、把angular.json中test目标的builder改为@angular/build:unit-test、迁移karma.conf.js中的自定义配置(reporter、plugin、自定义浏览器启动器分别对应 Vitest 的 reporters、第三方插件与browsers选项)、最后删除karma.conf.js与src/test.ts并卸载 karma 相关依赖。 - 自动化步骤:
unit-test构建器还提供实验性的ng g @schematics/angular:refactor-jasmine-vitestschematic,可自动把fit/fdescribe/xit/xdescribe转换为it.only/describe.only/it.skip/describe.skip、把spyOn转为vi.spyOn、把jasmine.any/objectContaining/createSpy转为expect.any/expect.objectContaining/vi.fn等;它不会自动安装依赖或改写angular.json,复杂 spy 场景仍需人工处理。 - 仍在使用
fakeAsync、flush、waitForAsync的旧测试,可通过把zone.js/plugins/vitest-patch加入test目标的 polyfills 来继续工作;官方推荐逐步向原生async与 Vitest fake timers 迁移。
更多测试主题
在完成测试环境配置后,官方文档中还有一系列面向具体场景的测试指南,可以按需查阅:
| 主题 | 内容要点 |
|---|---|
| 代码覆盖率 | 测试覆盖了应用的多大范围,以及如何设定覆盖率门槛 |
| 测试服务 | 如何在隔离环境中测试应用使用的各类服务(含依赖替换、spy 交互断言) |
| 组件测试基础 | 测试 Angular 组件的基础知识与 TestBed 用法 |
| 组件测试场景 | 各类组件测试场景与用例 |
| 测试属性型指令 | 如何测试属性指令 |
| 测试管道 | 如何测试 pipe |
| 调试测试 | 常见测试问题排查 |
| 测试工具 API | Angular 提供的测试特性 API |
上述指南与本文共同构成 Angular 单元测试的完整知识体系:以 ng test 与 angular.json 为日常操作主战场,以 TestBed(其核心实现在本仓库的 test_bed.ts,负责配置依赖注入并派发被测对象实例)为测试编排基础,再辅以覆盖率、浏览器模式与 CI 流水线,即可让单元测试在从本地开发到持续集成的全链路中持续为你的应用保驾护航。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00