通过启用 Angular 测试模块拆卸来改进 Angular 测试
封面照片由Marian Kroell在 Unsplash 上拍摄。
Angular 测试模块destroyAfterEach
拆卸选项解决了使用 Angular 测试平台时存在的几个长期问题:
- 直到创建另一个组件装置时,宿主元素才会从 DOM 中移除
- 组件样式永远不会从 DOM 中删除
- 应用程序范围的服务永远不会被破坏
- 使用任何提供程序范围的功能级服务永远不会被破坏
- Angular 模块永远不会被破坏
- 元件损坏次数少于测试次数1次
- 组件级服务被破坏的次数比测试次数少1次
当使用在浏览器中运行组件测试的 Karma 时,前两个问题影响最大。
你知道吗?
OnDestroy
Angular 模块和服务支持通过实现方法来挂钩生命周期时刻ngOnDestroy
。
在本指南中,我们:
- 探索
ModuleTeardownOptions#destroyAfterEach
Angular 测试平台的选项 - 列出 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);
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);
作为参考,TestBed.configureTestingModule
还接受teardown
Angular 12.1 及更高版本中的选项,如下代码片段所示:
TestBed.configureTestingModule({
teardown: { destroyAfterEach: true }, // 👈
// (...)
});
在 Jest 中启用 Angular 测试模块拆卸
如果我们的工作区或项目使用 Jest 进行单元测试,test-setup.ts
文件可能如下所示:
import 'jest-preset-angular/setup-jest';
要在 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 } }, // 👈
);
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 } }, // 👈
);
});
使用以下代码片段在一个或多个测试用例中退出 Angular 测试模块拆卸
import { TestBed } from '@angular/core/testing';
beforeEach(() => {
TestBed.configureTestingModule({
teardown: { destroyAfterEach: false }, // 👈
// (...)
});
});
如果已经创建了组件装置,我们必须TestBed.resetTestingModule
先调用TestBed.configureTestingModule
。
最后,可以migration-v13-testbed-teardown
通过应用以下命令命名的可选 Angular 迁移来选择退出整个工作区中的 Angular 测试模块拆卸:
ng update @angular/cli^13 --migrate-only=migration-v13-testbed-teardown
在得出结论之前,让我们讨论一下 Angular 测试模块拆卸对性能的影响。
性能影响
对性能的影响应该始终是积极的,但影响程度会受到以下因素的影响:
- 我们使用哪个测试运行器
- 我们正在运行多少个测试流程
- 我们在同一台主机上运行了多少个测试
我还没有在中型或大型代码库上进行过实验,但我的总体考虑是:
- 删除组件样式元素和宿主元素对 Karma 的影响最大,因为它在浏览器中运行测试,样式评估和 DOM 元素会消耗资源
- 销毁服务和 Angular 模块可防止重复的副作用,并释放可观察订阅、HTTP 请求和开放的 Web 套接字等资源。
Angular Components 团队使用 Karma 在 2017 年应用了具有此功能的 monkey patch,他们报告了更快、更可靠的测试。
结论
ModuleTeardownOptions#destroyAfterEach
当通过设置为 启用 Angular 测试模块拆卸时true
,Angular 测试平台通过触发以下生命周期时刻来管理测试用例运行之间的资源OnDestroy
:
- 应用级服务
- 功能级服务
- Angular 模块
- 成分
- 组件级服务
然而,ngOnDestroy
平台级服务的钩子从来不会在测试之间触发。
宿主元素和组件样式从 DOM 中删除,这在使用在浏览器中运行测试的 Karma 时尤为重要。
这一切都发生在调用TestBed.resetTestEnvironment
或TestBed.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.initTestEnvironment
teardown
destroyAfterEach
false
我们讨论了如何通过传递设置为teardown
的选项对象(可选择先调用 )来选择退出一个或多个测试用例上的 Angular 测试模块拆卸。destroyAfterEach
false
TestBed.configureTestinModule
TestBed.resetTestingModule
此外,我们还学习了如何应用migration-v13-testbed-teardown
迁移来退出整个工作区中的 Angular 测试模块拆卸。
最后,我们讨论了启用 Angular 测试模块拆卸功能的潜在性能影响。使用 Karma 时,潜在的性能影响最大,因为真实的 DOM 非常耗资源,而且当我们不断向文档添加样式表时,样式评估也非常耗资源。此外,Karma 默认不并行执行测试运行。
拆除 Angular 测试模块对于测试环境的正确性很重要,但请注意,平台范围内提供的依赖项永远不会被 Angular 测试平台隐式拆除。
后续步骤Next steps
将该选项设置ModuleTeardownOptions#destroyAfterEach
为true
隐式启用ModuleTeardownOptions#rethrowErrors
本指南未涵盖的选项。
在测试套件中启用 Angular 测试模块拆卸,并使用hyperfine 之类的工具来测量性能影响。
请让我知道您的性能影响以及启用此选项后是否有任何测试失败。
资源
本指南中的发现基于以下 Angular 拉取请求:
我写了几百个测试来比较启用和ModuleTeardownOptions#destroyAfterEach
禁用时初始化和拆卸的行为。如果你感兴趣,可以在github/LayZeeDK/angular-module-teardown-options找到它们。