ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Angular 测试迁移实操:用 RouterTestingModule migration 将测试平滑迁移到 RouterModule

2026/9/8 23:43:33 拓冰建站 浏览量
Angular 测试迁移实操:用 RouterTestingModule migration 将测试平滑迁移到 RouterModule Angular 测试迁移实操用 RouterTestingModule migration 将测试平滑迁移到 RouterModule【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angularAngular 官方在RouterTestingModule的源码级废弃注释中指出测试中应改用provideRouter或RouterModule/RouterModule.forRoot。angular/core提供的router-testing-module-migrationschematic 正是为此而生——它能把测试文件中RouterTestingModule的使用自动改写为RouterModule并在检测到SpyLocation.urlChanges这类依赖时自动补充provideLocationMocks()从而在迁移后保持原有测试行为完全不变。读完本文你将掌握这条迁移命令的完整用法、全部选项与精确的改写规则并通过仓库源码理解它的检测与重写原理。一、为什么需要这次迁移RouterTestingModule位于 packages/router/testing/src/router_testing_module.ts其 JSDoc 中明确标注了废弃原因deprecatedUseprovideRouterorRouterModule/RouterModule.forRootinstead. This module was previously used to provide a helpful collection of test fakes, most notably those forLocationandLocationStrategy. These are generally not required anymore, asMockPlatformLocationis provided inTestBedby default. However, you can use them directly withprovideLocationMocks.要点有二曾经的RouterTestingModule负责一次性提供一批测试替身test fakes最典型的是Location与LocationStrategy的 Spy 实现如今TestBed已默认提供MockPlatformLocation大部分场景不再需要这些替身真正需要时应直接使用provideLocationMocks()源码见 packages/common/testing/src/provide_location_mocks.ts。因此推荐的做法是测试模块的imports直接使用RouterModule或RouterModule.forRoot(routes, options)让TestBed负责默认的平台位置替身仅当测试确实依赖SpyLocation.urlChanges这类能力时再显式添加provideLocationMocks()。二、迁移命令与 Options在 Angular CLI 工作区中执行ng generate angular/core:router-testing-module-migration该 schematic 已注册在 packages/core/schematics/collection.json其description为 “Replaces deprecated RouterTestingModule with provideRouter() as recommended in the deprecation note”命令的工厂函数指向./bundles/router-testing-module-migration.cjs#migrate。由于它归属于angular/core包的 schematic 集合CLI 命令需要带上angular/core:前缀。选项说明选项详情path相对于项目根目录的迁移路径默认值为./。可用于只增量迁移项目的一部分。schema.json见 packages/core/schematics/ng-generate/router-testing-module-migration/schema.json中对该选项的描述为 “Path to the directory where all tests should be migrated.”并且在交互式运行时不带--path会给出提示“Which directory do you want to migrate?”默认./表示迁移整个项目的根目录。典型的分批迁移示例# 只迁移 src/app 下的测试 ng generate angular/core:router-testing-module-migration --pathsrc/app # 只迁移某个子模块的测试便于逐步验证 ng generate angular/core:router-testing-module-migration --pathsrc/app/features/dashboard分批执行的优势是每批改动较小、容易 code review也方便在 git 中精确定位回归来源。命令会处理哪些文件从 packages/core/schematics/ng-generate/router-testing-module-migration/index.ts 的shouldMigrate过滤逻辑可以精确得知作用范围必须同时满足三个条件file.rootRelativePath.startsWith(fs.normalize(options.path))—— 文件位于所选的path目录之下路径不匹配/(^|\/)node_modules\//—— 跳过依赖目录\.spec\.ts$结尾 —— 只处理单元测试文件。也就是说这是一个只针对*.spec.ts测试文件的迁移不会改动你的业务源码或应用模块。三、迁移规则总览整条迁移的语义可以用一份符号映射概括迁移前angular/router/testing迁移后angular/router/angular/common/testingRouterTestingModule裸引用RouterModuleRouterTestingModule.withRoutes(routes)RouterModule.forRoot(routes)RouterTestingModule.withRoutes(routes, options)RouterModule.forRoot(routes, options)保留 options测试中 import 了SpyLocation且访问了.urlChanges额外在providers中加入provideLocationMocks()其中路由选项会被完整保留例如{initialNavigation: enabledBlocking}、{preloadingStrategy: ...}这类传给withRoutes的第二参数会原样平移到forRoot的第二参数SpyLocation的 import 保持不变迁移只负责补上它背后所需的 providerangular/router/testing中其余命名导入不会被误删若删除RouterTestingModule后该 import 语句变空整行 import 会被移除。provideLocationMocks的触发条件在 packages/core/schematics/ng-generate/router-testing-module-migration/utils.ts 中实现得很明确文件里存在来自angular/common/testing的SpyLocation命名导入且该文件中存在对urlChanges属性的访问表达式。这是文件级file-scope扫描只要同一测试文件内任意位置出现spy.urlChanges之类的读取迁移就会为该文件中的相关TestBed配置补上provideLocationMocks()从而保证迁移前urlChanges数组能正常工作。四、官方迁移示例逐字讲解示例一保留路由选项withRoutes → forRoot迁移前import {RouterTestingModule} from angular/router/testing; import {SpyLocation} from angular/common/testing; describe(test, () { beforeEach(() { TestBed.configureTestingModule({ imports: [RouterTestingModule.withRoutes(routes, {initialNavigation: enabledBlocking})], }); }); });迁移后import {RouterModule} from angular/router; import {SpyLocation} from angular/common/testing; describe(test, () { beforeEach(() { TestBed.configureTestingModule({ imports: [RouterModule.forRoot(routes, {initialNavigation: enabledBlocking})], }); }); });可以看到RouterTestingModule.withRoutes(routes, {...})被替换为RouterModule.forRoot(routes, {...})第一参数路由表与第二参数ExtraOptions路由配置一一对应{initialNavigation: enabledBlocking}被完整保留。该场景对应的单元测试位于 packages/core/schematics/test/router-testing-module-migration_spec.ts测试断言迁移结果中不再包含RouterTestingModule。示例二SpyLocation urlChanges 场景补 provider迁移前import {RouterTestingModule} from angular/router/testing; import {SpyLocation} from angular/common/testing; describe(test, () { let spy: SpyLocation; beforeEach(() { TestBed.configureTestingModule({ imports: [RouterTestingModule], }); spy TestBed.inject(SpyLocation); }); it(Awesome test, () { expect(spy.urlChanges).toBeDefined(); }); });迁移后import {RouterModule} from angular/router; import {provideLocationMocks} from angular/common/testing; import {SpyLocation} from angular/common/testing; describe(test, () { let spy: SpyLocation; beforeEach(() { TestBed.configureTestingModule({ imports: [RouterModule], providers: [provideLocationMocks()], }); spy TestBed.inject(SpyLocation); }); it(Awesome test, () { expect(spy.urlChanges).toBeDefined(); }); });这段示例演示了完整的“保真迁移”裸引用的RouterTestingModule因为不携带路由表与选项直接退化为RouterModule不必调用forRoot由于文件 import 了SpyLocation并且存在spy.urlChanges的访问迁移自动往providers数组注入provideLocationMocks()同时从angular/common/testing补上provideLocationMocks的命名导入。示例三与其他测试模块共存时保持其余 imports当imports数组中除了RouterTestingModule还有其他模块时迁移只替换目标符号、保留其余元素。迁移前import {HttpClientTestingModule} from angular/common/http/testing; import {RouterTestingModule} from angular/router/testing; TestBed.configureTestingModule({ imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], });迁移后空路由数组、无 options 时会退化为裸RouterModuleimport {HttpClientTestingModule} from angular/common/http/testing; import {RouterModule} from angular/router; TestBed.configureTestingModule({ imports: [HttpClientTestingModule, RouterModule], });五、迁移器内部实现解析从源码结构看该迁移是一套典型的 Tsurge 类型感知迁移type-aware migration核心文件分工如下index.ts —— 入口与 Devkit Rule 包装负责文件过滤与迁移统计日志migration.ts —— 定义RouterTestingModuleMigration类实现 analyze / migrate / combine / stats 阶段utils.ts —— 真正的 AST 检测与文本重写工具types.ts ——MigrationConfig类型决定“哪些文件参与迁移”的判定函数。5.1 检测模式限定在 TestBed.configureTestingModule 内findRouterTestingModuleUsagesutils.ts的逻辑非常克制先确认该文件从angular/router/testing导入了RouterTestingModule遍历 AST寻找形如TestBed.configureTestingModule({...})的调用在该调用的配置对象中定位imports数组逐个元素检查是否为以下两种形态之一标识符RouterTestingModule裸引用调用表达式RouterTestingModule.withRoutes(...)此时取出其第一参数作为routesNode、第二参数作为optionsNode只有命中上述形态才记录一次RouterTestingModuleUsage并进入重写流程。这说明迁移器只处理出现在TestBed.configureTestingModule的imports数组里的RouterTestingModule对angular/router/testing模块常量、类型标注等其他引用方式不会强行改写避免产生不安全的代码。5.2 重写引擎路由与 options 的取舍createRouterModuleExpressionutils.ts负责把RouterTestingModule[.withRoutes(...)]转换为对应的RouterModule表达式并结合getRoutesArgumentForMigration做边界处理有非空路由表时生成RouterModule.forRoot(routes)若存在 options 则追加为第二参数路由表为空数组[]但带有 options 时仍生成RouterModule.forRoot([], options)路由表为空数组且无 options或原本就是裸RouterTestingModule时直接退化为裸标识符RouterModule。随后migrateToRouterModule会在原有imports数组中剔除RouterTestingModule元素、追加新的RouterModule表达式并据此重建整个数组字面量providers的处理与之类似——已有providers数组则追加元素缺失时则在imports属性之后自动插入形如,\n providers: [...]的新属性见 utils.ts。5.3 import 管理精准增删而非粗暴全量替换Import 的改写通过两套机制协作对angular/router/testing里的RouterTestingModule符号执行定向移除createImportRemovalReplacementutils.ts先检查该 import 是否还有其他命名成员若有则只删除目标符号并重写命名导入列表否则整条 import 语句一并删除对需要新增的RouterModule来自angular/router与provideLocationMocks来自angular/common/testing则交给ImportManager统一登记utils.ts由它负责去重、合并与排序等细节。5.4 多编译目标编排与结果统计由于index.ts中通过runMigrationInDevkit挂载了beforeProgramCreation/beforeUnitAnalysis/afterAllAnalyzed等生命周期回调执行时可以观察到如下日志输出Preparing analysis for: tsconfig 路径... Running migration for: tsconfig 路径... Scanning for RouterTestingModule usage: tsconfig 路径... Processing analysis data between targets... Successfully migrated RouterTestingModule to RouterModule - Migrated N RouterTestingModule usages in M test files. - Added provideLocationMocks() to K files with SpyLocation.urlChanges usage.其中最后的统计信息由 migration.ts 的stats阶段产出——migratedUsages计数实际被迁移的RouterTestingModule使用点totalFiles统计涉及的文件去重数量filesWithLocationMocks则记录需要补provideLocationMocks()的文件数。若某次运行显示K大于 0说明迁移判断有测试依赖SpyLocation.urlChanges这正是最需要人工复核行为是否被完整保留的场景。六、运行前检查与迁移后建议由于本次迁移会改动测试配置建议按以下步骤进行先跑一次不指定--path的试运行或在ng generate前借助 git 提交保存基线确认迁移器只报告了.spec.ts文件命令行日志中的文件路径会明确列出分批执行先用--path指向一个子目录迁移并运行对应测试验证通过后再扩大范围迁移完成后搜索确认代码库中不再出现从angular/router/testing导入RouterTestingModule的残留对曾经使用SpyLocation的测试重点检查urlChanges的断言仍然通过——这正是provideLocationMocks()补丁要守护的行为如果你的路由测试不再需要SpyLocation迁移后还可人工移除多余 import 与provideLocationMocks()因为当前TestBed已默认提供MockPlatformLocation。七、迁移结果的验证与回归测试该迁移器自身带有覆盖各类形态的单元测试位于 packages/core/schematics/test/router-testing-module-migration_spec.ts。测试套件通过SchematicTestRunner在虚拟文件系统中构造真实项目结构包括tsconfig.json、angular.json与node_modules下的桩声明文件再以runSchematic(router-testing-module-migration, {path}, tree)的方式驱动迁移并断言输出内容覆盖了诸如RouterTestingModule.withRoutes(routes)显式路由迁移到RouterModuleSpyLocation场景下provideLocationMocks()的补充各类 imports / providers 数组的增删与保留。若你希望对迁移行为做一次真实回归可参考该测试文件的搭建方式在自己的工作区构造一个最小复现用例先写好待迁移的*.spec.ts运行ng generate angular/core:router-testing-module-migration --path目录再对比前后 diff 是否与上文规则完全一致。总结router-testing-module-migration是 Angular 官方为配合RouterTestingModule废弃而提供的一键式迁移工具。它的核心价值在于“行为保真”不仅能精确地把RouterTestingModule/RouterTestingModule.withRoutes(routes, options)重写为等价的RouterModule/RouterModule.forRoot(routes, options)还能通过文件级检测识别出依赖SpyLocation.urlChanges的测试并自动补充provideLocationMocks()。从 index.ts、utils.ts 与 migration.ts 的实现可以确认它严格限定.spec.ts、跳过node_modules、可配合--path分批推进并只改写TestBed.configureTestingModuleimports数组中的目标符号。借助--dry-run式的分步验证和本文第六节的检查清单你可以把整个仓库的 Router 测试平滑、安全地迁移到新 API 之上。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考