原文:Testing and faking Angular dependencies
依赖注入是 Angular 的一个要害个性。这种灵便的办法使咱们的可申明和基于类的服务更容易隔离测试。
可摇树依赖项移除了间接层 即 Angular 模块,但咱们如何测试它们的可摇树 provider?咱们将测试依赖于特定平台 API 的注入令牌的值工厂。
某些组件具备特定于浏览器的性能。咱们将一起测试告诉用户咱们将终止 Internet Explorer 11 反对的横幅。一个适合的测试套件能够给咱们足够的信念,咱们甚至不用在 Internet Explorer 11 中测试横幅。
咱们必须小心不要对简单的集成场景过于自信。咱们应该始终确保在尽可能靠近生产的环境中执行 QA(质量保证)测试。这意味着在 实在 Internet Explorer 11 浏览器中运行应用程序。
Angular 测试实用程序使咱们可能伪造依赖项以进行测试。咱们将应用 Angular CLI 的测试框架 Jasmine 摸索在 Angular 测试环境中配置和解决依赖关系的不同选项。
通过示例,咱们将摸索组件 fixtures、组件初始化、自定义 expectations、模仿事件。咱们甚至会为十分精简但明确的测试用例创立自定义测试工具。
Faking dependency injection tokens used in token providers
看个例子。
咱们创立了一个依赖注入令牌,该令牌评估为批示以后浏览器是否为 Internet Explorer 11 的标记。
// user-agent.token.ts
import {InjectionToken} from '@angular/core';
export const userAgentToken: InjectionToken<string> =
new InjectionToken('User agent string', {factory: (): string => navigator.userAgent,
providedIn: 'root',
});
// is-internet-explorer-11.token.ts
import {inject, InjectionToken} from '@angular/core';
import {userAgentToken} from './user-agent.token';
export const isInternetExplorer11Token: InjectionToken<boolean> =
new InjectionToken('Internet Explorer 11 flag', {factory: (): boolean =>
/Trident\/7\.0.+rv:11\.0/.test(inject(userAgentToken)),
providedIn: 'root',
});
为了独自测试 Internet Explorer 11 标记提供程序,咱们能够用一个假值替换 userAgentToken。
咱们留神到用户代理字符串提供程序从特定于平台的 Navigator API 中提取相干信息。为了学习,假如咱们将须要来自同一个全局导航器对象的其余信息。依据咱们应用的测试运行器,Navigator API 甚至可能在测试环境中不可用。
为了可能创立虚伪的导航器配置,咱们为导航器 API 创立了一个依赖注入令牌。咱们能够在开发和测试期间应用这些虚伪配置来模仿用户上下文。
// user-agent.token.ts
import {inject, InjectionToken} from '@angular/core';
import {navigatorToken} from './navigator.token';
export const userAgentToken: InjectionToken<string> =
new InjectionToken('User agent string', {factory: (): string => inject(navigatorToken).userAgent,
providedIn: 'root',
});
// navigator.token.ts
import {InjectionToken} from '@angular/core';
export const navigatorToken: InjectionToken<Navigator> =
new InjectionToken('Navigator API', {factory: (): Navigator => navigator,
providedIn: 'root',
});
对于咱们的第一个测试,咱们将为 Navigator API 令牌提供一个假值,该令牌在工厂提供程序中用作用户代理字符串令牌的依赖项。
为了出于测试目标替换令牌提供程序,咱们在 Angular 测试模块中增加了一个笼罩提供程序,相似于 Angular 模块本人的提供程序如何笼罩导入的 Angular 模块的提供程序。
// navigator-api.spec.ts
import {inject, TestBed} from '@angular/core/testing';
import {navigatorToken} from './navigator.token';
import {userAgentToken} from './user-agent.token';
describe('Navigator API', () => {describe('User agent string', () => {describe('Provider', () => {beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: navigatorToken,
useValue: {userAgent: 'Fake browser',},
},
],
});
});
it(
'extracts the user agent string from the Navigator API token',
inject([userAgentToken], (userAgent: string) => {expect(userAgent).toBe('Fake browser');
}));
});
});
});
请留神,尽管咱们正在测试的是 user agent 令牌及其提供者,但咱们正在用假值替换 navigator 令牌依赖项。
Resolving dependencies using the inject function
Angular 测试实用程序为咱们提供了不止一种解决依赖关系的办法。在这个测试中,咱们应用 @angular/core/testing 包中的 inject 函数(* 不是 @angular/core 中的那个)。
注入函数容许咱们通过在咱们作为参数传递的数组中列出它们的标记来解决多个依赖项。每个依赖注入令牌都被解析并作为参数提供给测试用例函数。
例子:https://stackblitz.com/edit/t…
Gotchas when using the Angular testing function inject
当咱们应用没有申明的 Angular 测试模块时,即便在同一个测试用例中,咱们通常也能够屡次笼罩 provider. 咱们将在本文前面钻研一个例子。
值得注意的是,在应用 Angular 测试性能 inject 时,状况并非如此。它在执行测试用例函数体之前解决依赖关系。
咱们能够应用静态方法 TestBed.configureTestingModule 和 TestBed.overrideProvider 替换 beforeAll 和 beforeEach 钩子中的令牌提供者。然而当咱们应用注入测试性能来解决依赖关系时,咱们不能在测试用例之间扭转提供者或在测试用例期间替换它。
在没有 declarables 的测试中解决 Angular 依赖关系的一种更灵便的办法是应用静态方法 TestBed.get。咱们只需从测试用例函数或测试生命周期钩子的任何中央传递咱们想要解析的依赖注入令牌。
让咱们看另一个原生浏览器 API 示例,咱们应用依赖注入令牌对其进行形象,以进行开发和测试。
Location 依赖于 Document:
// location.token.ts
import {DOCUMENT} from '@angular/common';
import {inject, InjectionToken} from '@angular/core';
export const locationToken: InjectionToken<Location> =
new InjectionToken('Location API', {factory: (): Location => inject(DOCUMENT).location,
providedIn: 'root',
});
// location-api.spec.ts
import {DOCUMENT} from '@angular/common';
import {TestBed} from '@angular/core/testing';
import {locationToken} from './location.token';
describe('Location API', () => {describe('Provider', () => {it('extracts the location from the DOCUMENT token', () => {
TestBed.configureTestingModule({
providers: [
{
provide: DOCUMENT,
useValue: {
location: {href: 'Fake URL',},
},
},
],
});
const location: Location = TestBed.get(locationToken);
expect(location.href).toBe('Fake URL');
});
});
});
咱们通过应用动态 TestBed.get 办法使 Angular 依赖注入零碎解析 Location API。正如 StackBlitz 测试项目中所证实的那样,文档令牌被胜利伪造并用于应用其实在的工厂提供程序来解析被测令牌。
Gotchas when resolving dependencies using TestBed
在之前的测试中,咱们通过在 Angular 测试模块中为 DOCUMENT 令牌提供文档来将文档替换为假对象。如果咱们没有这样做,Angular 就会提供全局文档对象。
此外,如果咱们想测试不同的文档配置,如果咱们没有为文档令牌创立 test provider,咱们将无奈这样做。
在咱们应用 TestBed.configureTestingModule 增加测试提供程序的状况下,咱们能够应用静态方法 TestBed.overrideProvider 在各种测试用例中将其替换为不同的假值。在测试 Internet Explorer 11 检测和 Internet Explorer 11 横幅组件时,咱们将应用此技术创立测试工具。
请留神,这是惟一可能的,因为咱们不应用 declarable。一旦咱们调用 TestBed.createComponent,Angular 测试平台的依赖就被锁定了。
Testing value factories with dependencies
在本文的第一局部中,咱们介绍了一个在其提供程序中带有值工厂的令牌。值工厂评估用户代理字符串是否代表 Internet Explorer 11 浏览器。
为了测试值工厂中的浏览器检测,咱们从实在浏览器中收集了一些用户代理字符串并将它们放在一个枚举中。
// fake-user-agent.ts
export enum FakeUserAgent {Chrome = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
InternetExplorer10 = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)',
InternetExplorer11 = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko',
Firefox = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0',
}
在 Internet Explorer 11 检测测试套件中,咱们将简直孤立地测试 isInternetExplorer11Token。但真正的业务逻辑价值在于它的工厂提供者,它依赖于用户代理令牌。
用户代理令牌从 Navigator API 令牌中提取其值,但 Navigator API 测试套件已涵盖该依赖项。咱们将抉择用户代理令牌作为依赖链中适合的地位来开始伪造依赖。
// internet-explorer-11-detection.spec.ts
import {TestBed} from '@angular/core/testing';
import {isInternetExplorer11Token} from './is-internet-explorer-11.token';
import {FakeUserAgent} from './fake-user-agent';
import {userAgentToken} from './user-agent.token';
describe('Internet Explorer 11 detection', () => {function setup({ userAgent}: {userAgent: string}) {TestBed.overrideProvider(userAgentToken, { useValue: userAgent});
return {isInternetExplorer11: TestBed.get(isInternetExplorer11Token),
};
}
const nonInternetExplorerUserAgents: ReadonlyArray<string> =
Object.entries(FakeUserAgent)
.filter(([browser]) =>
!browser.toLowerCase().includes('internetexplorer'))
.map(([_browser, userAgent]) => userAgent);
it('accepts an Internet Explorer 11 user agent', () => {const { isInternetExplorer11} = setup({userAgent: FakeUserAgent.InternetExplorer11,});
expect(isInternetExplorer11).toBe(true);
});
it('rejects an Internet Explorer 10 user agent', () => {const { isInternetExplorer11} = setup({userAgent: FakeUserAgent.InternetExplorer10,});
expect(isInternetExplorer11).toBe(false);
});
it('rejects other user agents', () => {
nonInternetExplorerUserAgents.forEach(userAgent => {const { isInternetExplorer11} = setup({userAgent});
expect(isInternetExplorer11).toBe(
false,
`Expected to reject user agent: "${userAgent}"`);
});
});
});
在指定测试用例之前,咱们创立了一个测试设置函数,并从咱们的假用户代理字符串中缩小了一组非 Internet Explorer 用户代理字符串。
测试设置函数采纳用户代理并应用它来伪造用户代理令牌提供者。而后咱们返回一个具备属性 isInternetExplorer11 的对象,该对象具备通过 TestBed.get 办法从 isInternetExplorer11Token 评估的值。
让咱们先测试一下高兴门路。咱们传递 Internet Explorer 11 用户代理字符串,并冀望被测令牌通过 Angular 的依赖注入零碎评估为 true。正如 StackBlitz 测试项目中所见,浏览器检测按预期工作。
当用户应用 Internet Explorer 10 浏览时会产生什么?咱们的测试套件表明 Internet Explorer 11 在这种状况下不会导致误报。
换句话说,当依赖令牌中提供 Internet Explorer 10 用户代理字符串时,被测令牌评估为 false。如果这不是预期用处,咱们须要更改检测逻辑。当初咱们曾经进行了测试,很容易证实该更改何时会胜利。
最初的测试在 FakeUserAgent 枚举定义的非 Internet Explorer 浏览器上执行浏览器检测。测试用例遍历用户代理字符串,伪造用户代理提供程序,评估 isInternetExplorer11Token 并冀望其值为 false。如果不是这种状况,测试运行程序会显示有用的谬误音讯。
Faking dependencies in component tests
当初咱们对 Internet Explorer 11 浏览器检测感到称心,创立和显示弃用横幅很简略。
<!-- internet-explorer-11-banner.component.html -->
<aside *ngIf="isBannerVisible">
Sorry, we will not continue to support Internet Explorer 11.<br />
Please upgrade to Microsoft Edge.<br />
<button (click)="onDismiss()">
Dismiss
</button>
</aside>
// internet-explorer-11-banner.component.ts
import {Component, Inject} from '@angular/core';
import {isInternetExplorer11Token} from './is-internet-explorer-11.token';
@Component({
selector: 'internet-explorer-11-banner',
templateUrl: './internet-explorer-11-banner.component.html',
})
export class InternetExplorer11BannerComponent {
private isDismissed = false;
get isBannerVisible() {return this.isInternetExplorer11 && !this.isDismissed;}
constructor(@Inject(isInternetExplorer11Token) private isInternetExplorer11: boolean,
) {}
onDismiss() {this.isDismissed = true;}
}
解除状态只是作为本地 UI 状态存储在公有组件属性中,该属性由计算属性 isBannerVisible 应用。
横幅组件有一个依赖项——isInternetExplorer11Token,它被评估为一个布尔值。因为 Inject 装璜器,这个布尔值是通过横幅组件构造函数注入的。
Summary
在本文中,咱们演示了如何在 Angular 我的项目中测试和伪造 tree-shakable 依赖项。咱们还测试了依赖于平台特定 API 的价值工厂。
在此过程中,咱们考察了应用注入测试性能解决依赖项时的问题。应用 TestBed,咱们解决了依赖注入令牌并摸索了这种办法的陷阱。
咱们以多种形式测试了 Internet Explorer 11 弃用横幅,以至于简直不须要在理论浏览器中对其进行测试。咱们在它的组件测试套件中伪造了它的依赖项,但正如咱们所探讨的,咱们应该始终在简单的集成场景的实在浏览器指标中测试它。