通过启用 Angular 测试模块拆卸来改进 Angular 测试

2025-06-11

通过启用 Angular 测试模块拆卸来改进 Angular 测试

封面照片由Marian Kroell在 Unsplash 上拍摄。

Angular 测试模块destroyAfterEach拆卸选项解决了使用 Angular 测试平台时存在的几个长期问题:

  • 直到创建另一个组件装置时,宿主元素才会从 DOM 中移除
  • 组件样式永远不会从 DOM 中删除
  • 应用程序范围的服务永远不会被破坏
  • 使用任何提供程序范围的功能级服务永远不会被破坏
  • Angular 模块永远不会被破坏
  • 元件损坏次数少于测试次数1次
  • 组件级服务被破坏的次数比测试次数少1次

当使用在浏览器中运行组件测试的 Karma 时,前两个问题影响最大。

你知道吗?OnDestroy Angular 模块和服务支持通过实现方法来挂钩生命周期时刻ngOnDestroy

在本指南中,我们:

  • 探索ModuleTeardownOptions#destroyAfterEachAngular 测试平台的选项
  • 列出 Karma 和 Jest 的完整 Angular 测试模块拆卸配置以供参考
  • 检查如何在测试套件或测试用例中选择加入或退出 Angular 测试模块拆卸
  • 讨论启用 Angular 测试模块拆卸的潜在性能影响
  • 讨论 Angular 测试模块的注意事项和剩余问题

探索 Angular 测试模块 destroyAfterEach 的拆卸选项

Angular 版本 12.1 添加了teardown选项对象ModuleTeardownOptions,可以将其传递TestBed.configureTestingModule给测试用例或TestBed.initTestEnvironment作为全局设置。

我们可以将destroyAfterEach选项作为选项对象的一部分来启用teardown。这反过来又启用了rethrowErrors本指南未涵盖的选项。

在 Angular 12.1 和 12.2 版本中,ModuleTeardownOptions#destroyAfterEach默认值为false。在 Angular 13.0 及更高版本中,其默认值为true

启用后destroyAfterEach,每个测试用例之后或以其他方式触发测试模块拆卸时会发生以下情况:

  • 宿主元素从 DOM 中移除
  • 组件样式已从 DOM 中移除
  • 应用程序范围的服务被破坏
  • 使用任何提供程序范围的功能级服务都将被销毁
  • Angular 模块被破坏
  • 组件被破坏
  • 组件级服务被破坏

Angular 测试陷阱:平台级服务在 Angular 测试中永远不会被破坏。

Angular 测试拆卸触发器

destroyAfterEach启用时,以下事件会触发 Angular 测试拆卸:

  • TestBed.resetTestEnvironment被称为
  • TestBed.resetTestingModule被称为
  • 测试用例完成

接下来,让我们看一下 Karma 和 Jest 测试运行器的完整配置示例。

在 Karma 中启用 Angular 测试模块拆卸

直到 Angular 版本 12.1(含)以及 Angular 13.0 和更高版本中,生成的主 Karma 测试文件(test.ts)如下所示:

// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'zone.js/dist/zone';

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
Enter fullscreen mode Exit fullscreen mode
Angular 12.1 和 13.0 版本生成的 test.ts

Angular 版本 12.1 添加了第 3 个参数,TestBed.initTestEnvironment如 Angular 版本 12.2 生成的以下代码片段所示:

// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'zone.js/dist/zone';

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
  { teardown: { destroyAfterEach: true } }, // 👈
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
Enter fullscreen mode Exit fullscreen mode
Angular 12.2 版本生成的 test.ts

作为参考,TestBed.configureTestingModule还接受teardownAngular 12.1 及更高版本中的选项,如下代码片段所示:

TestBed.configureTestingModule({
  teardown: { destroyAfterEach: true }, // 👈
  // (...)
});
Enter fullscreen mode Exit fullscreen mode
测试套件设置启用 Angular 测试模块拆卸

在 Jest 中启用 Angular 测试模块拆卸

如果我们的工作区或项目使用 Jest 进行单元测试,test-setup.ts文件可能如下所示:

import 'jest-preset-angular/setup-jest';
Enter fullscreen mode Exit fullscreen mode
test-setup.ts 包含 Jest 的 Angular 预设

要在 Angular 版本 12.1 和 12.2 中启用 Angular 测试模块拆卸,请使用以下代码:

import 'jest-preset-angular/setup-jest';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

getTestBed().resetTestEnvironment();
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
  { teardown: { destroyAfterEach: true } }, // 👈
);
Enter fullscreen mode Exit fullscreen mode
带有 Angular 测试模块拆卸的 Jest 的 test-setup.ts

Jest 的 Angular 预设已经初始化了 Angular 测试平台环境,因此我们必须在配置和初始化 Angular 测试平台环境之前重置它。

在全局启用 Angular 测试模块拆卸之后,让我们继续选择退出 Angular 测试模块拆卸。

禁用 Angular 测试模块拆卸

如果我们的 Angular 测试在启用 Angular 测试模块拆卸后中断,我们可以全局或本地选择退出。

我们可能想要退出,因为各种 Angular 测试库在destroyAfterEach启用时可能会中断,或者它们可能不接受或指定此选项。

使用以下代码片段可以退出整个测试套件中的 Angular 测试模块拆卸:

import { TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

beforeAll(() => {
  TestBed.resetTestEnvironment();
  TestBed.initTestEnvironment(
    BrowserDynamicTestingModule,
    platformBrowserDynamicTesting(),
    { teardown: { destroyAfterEach: false } }, // 👈
  );
});
Enter fullscreen mode Exit fullscreen mode

使用以下代码片段在一个或多个测试用例中退出 Angular 测试模块拆卸

import { TestBed } from '@angular/core/testing';

beforeEach(() => {
  TestBed.configureTestingModule({
    teardown: { destroyAfterEach: false }, // 👈
    // (...)
  });
});
Enter fullscreen mode Exit fullscreen mode

如果已经创建了组件装置,我们必须TestBed.resetTestingModule先调用TestBed.configureTestingModule

最后,可以migration-v13-testbed-teardown通过应用以下命令命名的可选 Angular 迁移来选择退出整个工作区中的 Angular 测试模块拆卸:

ng update @angular/cli^13 --migrate-only=migration-v13-testbed-teardown
Enter fullscreen mode Exit fullscreen mode

在得出结论之前,让我们讨论一下 Angular 测试模块拆卸对性能的影响。

性能影响

对性能的影响应该始终是积极的,但影响程度会受到以下因素的影响:

  • 我们使用哪个测试运行器
  • 我们正在运行多少个测试流程
  • 我们在同一台主机上运行了多少个测试

我还没有在中型或大型代码库上进行过实验,但我的总体考虑是:

  • 删除组件样式元素和宿主元素对 Karma 的影响最大,因为它在浏览器中运行测试,样式评估和 DOM 元素会消耗资源
  • 销毁服务和 Angular 模块可防止重复的副作用,并释放可观察订阅、HTTP 请求和开放的 Web 套接字等资源。

Angular Components 团队使用 Karma 在 2017 年应用了具有此功能的 monkey patch,他们报告了更快、更可靠的测试。

结论

ModuleTeardownOptions#destroyAfterEach当通过设置为 启用 Angular 测试模块拆卸时true,Angular 测试平台通过触发以下生命周期时刻来管理测试用例运行之间的资源OnDestroy

  • 应用级服务
  • 功能级服务
  • Angular 模块
  • 成分
  • 组件级服务

然而,ngOnDestroy平台级服务的钩子从来不会在测试之间触发。

宿主元素和组件样式从 DOM 中删除,这在使用在浏览器中运行测试的 Karma 时尤为重要。

这一切都发生在调用TestBed.resetTestEnvironmentTestBed.resetTestingModule最迟在测试用例完成时。

我们讨论了 Angular 版本 12.1 是如何ModuleTeardownOptions引入的,但是原理图生成的值和默认值在 Angular 版本 12.2 和 13.0 中发生了变化,如下表所示:

角度版本 默认值destroyAfterEach Schematics 生成的值destroyAfterEach
<=12.0 不适用 不适用
12.1 false 不适用
12.2 false true
>=13.0 true 不适用

在Karma 中启用 Angular 测试模块拆卸在 Jest 中启用 Angular 测试模块拆卸部分中,我们引用了 Karma 和 Jest 测试运行器的完整示例全局 Angular 测试模块拆卸配置。

我们了解了如何通过调用并TestBed.resetTestEnvironment随后指定设置为的选项来在全局范围内选择退出 Angular 测试模块拆卸TestBed.initTestEnvironmentteardowndestroyAfterEachfalse

我们讨论了如何通过传递设置teardown的选项对象(可选择先调用 )来选择退出一个或多个测试用例上的 Angular 测试模块拆卸destroyAfterEachfalseTestBed.configureTestinModuleTestBed.resetTestingModule

此外,我们还学习了如何应用migration-v13-testbed-teardown迁移来退出整个工作区中的 Angular 测试模块拆卸。

最后,我们讨论了启用 Angular 测试模块拆卸功能的潜在性能影响。使用 Karma 时,潜在的性能影响最大,因为真实的 DOM 非常耗资源,而且当我们不断向文档添加样式表时,样式评估也非常耗资源。此外,Karma 默认不并行执行测试运行。

拆除 Angular 测试模块对于测试环境的正确性很重要,但请注意,平台范围内提供的依赖项永远不会被 Angular 测试平台隐式拆除。

后续步骤Next steps

将该选项设置ModuleTeardownOptions#destroyAfterEachtrue隐式启用ModuleTeardownOptions#rethrowErrors本指南未涵盖的选项。

在测试套件中启用 Angular 测试模块拆卸,并使用hyperfine 之类的工具来测量性能影响。

请让我知道您的性能影响以及启用此选项后是否有任何测试失败。

资源

本指南中的发现基于以下 Angular 拉取请求:

我写了几百个测试来比较启用和ModuleTeardownOptions#destroyAfterEach禁用时初始化和拆卸的行为。如果你感兴趣,可以在github/LayZeeDK/angular-module-teardown-options找到它们。

链接已修复:https://dev.to/this-is-angular/improving-angular-tests-by-enabling-angular-testing-module-teardown-38kh
PREV
信号:该做什么和不该做什么
NEXT
动态表情符号 欢迎来到 DevTools 欢迎来到 DevTools