Angular 应用级别的依赖 Fake
原文:Faking dependencies in Angular applications
使用 Angular 依賴注入系統(tǒng)的強大功能,我們可以偽造特定的用例。 這對于自動化測試很有用,但在本文中,我們將研究一種將其用于手動測試的方法。
為了讓我們的生活更輕松,我們將創(chuàng)建一個瀏覽器偽造組件,由于自定義結(jié)構(gòu)指令,該組件僅在開發(fā)模式下啟用。 為了好玩,我們將添加文本管道以在我們的組件模板中使用常見的字符串操作。
Simulating a browser environment
Dynamically replacing a dependency using a class-based service
用戶代理令牌工廠只對每個模塊注入器評估一次,如果它沒有被祖先組件或指令提供的元素注入器替換,我們必須使用另一種技術(shù)來偽造依賴項。 我們將使用基于類的服務(wù)依賴替換依賴注入令牌依賴。
// internet-explorer-11-banner.component.ts import { Component } from '@angular/core';import { InternetExplorerService } from './internet-explorer.service';@Component({selector: 'internet-explorer-11-banner',templateUrl: './internet-explorer-11-banner.component.html', }) export class InternetExplorer11BannerComponent {private isDismissed = false;get isBannerVisible() {return this.internetExplorer.isInternetExplorer11State && !this.isDismissed;}constructor(private internetExplorer: InternetExplorerService,) {}onDismiss() {this.isDismissed = true;} } // internet-explorer-service.ts import { Inject, Injectable } from '@angular/core';import { userAgentToken } from './user-agent.token';@Injectable({providedIn: 'root', }) export class InternetExplorerService {get isInternetExplorer11State(): boolean {return this.isInternetExplorer11(this.userAgent);}constructor(@Inject(userAgentToken) private userAgent: string,) {}isInternetExplorer11(userAgent: string): boolean {return /Trident\/7\.0.+rv:11\.0/.test(userAgent);} }首先,我們從依賴注入令牌中提取 Internet Explorer 11 檢測到我們新創(chuàng)建的 InternetExplorerService 類。 Internet Explorer 11 檢測令牌現(xiàn)在在根據(jù)用戶代理評估其值時委托給服務(wù)。
如前所述,我們不會使用元素注入器在模板中以聲明方式動態(tài)替換用戶代理令牌。 相反,我們將強制更改狀態(tài)。
Creating an observable state
下面展示的辦法不使用 userAgent token 的 injection token,而是使用 Observable. 這個 Observable 對象從另一個 Browser service 里獲得。
// internet-explorer.service.ts import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators';import { BrowserService } from './browser.service';@Injectable({providedIn: 'root', }) export class InternetExplorerService {isInternetExplorer11$: Observable<boolean> =this.browser.userAgent$.pipe(map(userAgent => this.isInternetExplorer11(userAgent)),);constructor(private browser: BrowserService,) {}isInternetExplorer11(userAgent: string): boolean {return /Trident\/7\.0.+rv:11\.0/.test(userAgent);} }browser service 實現(xiàn)里,還是會使用 user agent injection token:
// browser.service.tsimport { Inject, Injectable, OnDestroy } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators';import { FakeUserAgent } from './fake-user-agent'; import { userAgentToken } from './user-agent.token';@Injectable({providedIn: 'root', }) export class BrowserService implements OnDestroy {// 這體現(xiàn)了 Observable 和 BehaviorSubject 的區(qū)別:后者實例化時,需要一個初始值:private userAgent = new BehaviorSubject(this.realUserAgent);userAgent$ = this.userAgent.pipe(distinctUntilChanged(),);constructor(@Inject(userAgentToken) private realUserAgent: string,) {}ngOnDestroy() {this.userAgent.complete();}fakeUserAgent(value: FakeUserAgent) {this.userAgent.next(FakeUserAgent[value]);}stopFakingUserAgent() {this.userAgent.next(this.realUserAgent);} }我們將當(dāng)前用戶代理狀態(tài)存儲在 BehaviorSubject 中,它暴露在 BrowserService 的可觀察 userAgent$ 屬性中。 當(dāng)整個應(yīng)用程序需要用戶代理時,它應(yīng)該依賴于這個 observable。
最初,behavior subject 的初始值來自用戶代理令牌的真實用戶代理字符串。 該值也被存儲以備后用,因為我們允許通過兩個命令更改瀏覽器狀態(tài)。
我們公開了 fakeUserAgent 方法,該方法將用戶代理狀態(tài)設(shè)置為假用戶代理字符串。 此外,我們允許依賴者調(diào)用 stopFakingUserAgent 方法,該方法將用戶代理狀態(tài)重置為真實的用戶代理字符串。
Internet Explorer Service 現(xiàn)在公開一個名為 isInternetExplorer11$ 的可觀察屬性,只要瀏覽器服務(wù)的可觀察用戶代理屬性發(fā)出值,就會評估該屬性。
The Internet Explorer service now exposes an observable property called isInternetExplorer11$ which is evaluated whenever the observable user agent property of the browser service emits a value.
我們現(xiàn)在需要的只是讓棄用橫幅組件依賴于可觀察的 Internet Explorer 11 檢測屬性,而不是我們替換的常規(guī)屬性。
<!-- internet-explorer-11-banner.component.html --> <aside *ngIf="isBannerVisible$ | async">Sorry, we will not continue to support Internet Explorer 11.<br />Please upgrade to Microsoft Edge.<br /><button (click)="onDismiss()">Dismiss</button> </aside>現(xiàn)在 banner 是否 visible,是由兩個 boolean 值控制了,所以使用 combineLatest.
// internet-explorer-11-banner.component.ts import { Component } from '@angular/core'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { map } from 'rxjs/operators';import { InternetExplorerService } from './internet-explorer.service';@Component({host: { style: 'display: block;' },selector: 'internet-explorer-11-banner',templateUrl: './internet-explorer-11-banner.component.html', }) export class InternetExplorer11BannerComponent {private isDismissed = new BehaviorSubject(false);isBannerVisible$ = combineLatest(this.internetExplorer.isInternetExplorer11$,this.isDismissed,).pipe(map(([isInternetExplorer11, isDismissed]) =>isInternetExplorer11 && !isDismissed),);constructor(private internetExplorer: InternetExplorerService,) {}onDismiss(): void {this.isDismissed.next(true);} }在棄用橫幅組件中,我們將 Boolean isDismissed 屬性替換為 BehaviorSubject ,該屬性最初被清除(設(shè)置為 false)。 我們現(xiàn)在有一個可觀察的 isBannerVisible$ 屬性,它是來自 isDismissed 和 InternetExplorerService#isInternetExplorer11$ 的可觀察狀態(tài)的組合。 UI 行為邏輯與之前類似,不同之處在于它現(xiàn)在表示為 observable 管道的一部分。
現(xiàn)在,onDismiss 事件處理程序不再為屬性分配布爾值,而是通過 isDismissed 行為主體發(fā)出布爾值。
此時,應(yīng)用程序的行為與我們引入 Internet Explorer 服務(wù)和瀏覽器服務(wù)之前的行為完全相同。 我們有瀏覽器狀態(tài)更改命令,但我們需要某種機制來觸發(fā)它們。
為此,我們將開發(fā)一個瀏覽器偽造器組件,使我們能夠為應(yīng)用程序的其余部分偽造瀏覽器環(huán)境。
<!-- browser-faker.component.html --> <label>Fake a browser<select [formControl]="selectedBrowser"><option value="">My browser</option><option *ngFor="let browser of browsers"[value]="browser">{{browser | replace:wordStartPattern:' $&' | trim}}</option></select> </label> // browser-faker.component.ts import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { Observable, Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators';import { BrowserService } from './browser.service'; import { FakeUserAgent } from './fake-user-agent';@Component({host: { style: 'display: block;' },selector: 'browser-faker',templateUrl: './browser-faker.component.html', }) export class BrowserFakerComponent implements OnDestroy, OnInit {private defaultOptionValue = '';private destroy = new Subject<void>();private fakeBrowserSelection$: Observable<FakeUserAgent>;private realBrowserSelection$: Observable<void>;browsers = Object.keys(FakeUserAgent);selectedBrowser = new FormControl(this.defaultOptionValue);wordStartPattern = /[A-Z]|\d+/g;constructor(private browser: BrowserService,) {this.realBrowserSelection$ = this.selectedBrowser.valueChanges.pipe(filter(value => value === this.defaultOptionValue),takeUntil(this.destroy),);this.fakeBrowserSelection$ = this.selectedBrowser.valueChanges.pipe(filter(value => value !== this.defaultOptionValue),takeUntil(this.destroy),);}ngOnInit(): void {this.bindEvents();}ngOnDestroy() {this.unbindEvents();}private bindEvents(): void { // 一旦這個 Observable 有事件發(fā)生,說明用戶選擇了 fake browserthis.fakeBrowserSelection$.subscribe(userAgent =>this.browser.fakeUserAgent(userAgent));this.realBrowserSelection$.subscribe(() =>this.browser.stopFakingUserAgent());}private unbindEvents(): void {this.destroy.next();this.destroy.complete();} }browser faker 組件注入瀏覽器服務(wù)。 它有一個綁定到本機 select 控件的表單控件。 選擇瀏覽器后,我們開始通過瀏覽器服務(wù)偽造其用戶代理。 選擇默認(rèn)瀏覽器選項后,我們會停止偽造用戶代理。
現(xiàn)在我們有一個瀏覽器偽造組件,但我們只希望在開發(fā)過程中啟用它。 讓我們創(chuàng)建一個僅在開發(fā)模式下有條件地呈現(xiàn)的結(jié)構(gòu)指令。
創(chuàng)建一個 injection token:
// is-development-mode.token.ts import { InjectionToken, isDevMode } from '@angular/core';export const isDevelopmentModeToken: InjectionToken<boolean> =new InjectionToken('Development mode flag', {factory: (): boolean => isDevMode(),providedIn: 'root',}); // development-only.directive.ts import {Directive,Inject,OnDestroy,OnInit,TemplateRef,ViewContainerRef, } from '@angular/core';import { isDevelopmentModeToken } from './is-development-mode.token';@Directive({exportAs: 'developmentOnly',selector: '[developmentOnly]', }) export class DevelopmentOnlyDirective implements OnDestroy, OnInit {private get isEnabled(): boolean {return this.isDevelopmentMode;}constructor(private container: ViewContainerRef,private template: TemplateRef<any>,@Inject(isDevelopmentModeToken) private isDevelopmentMode: boolean,) {}ngOnInit(): void {if (this.isEnabled) {this.createAndAttachView();}}ngOnDestroy(): void {this.destroyView();}private createAndAttachView(): void {this.container.createEmbeddedView(this.template);}private destroyView(): void {this.container.clear();} }如果應(yīng)用程序在開發(fā)模式下運行,則此結(jié)構(gòu)指令僅呈現(xiàn)它所附加的組件或元素,正如其測試套件所驗證的那樣。
現(xiàn)在,剩下的就是將棄用橫幅和瀏覽器偽裝器添加到我們的應(yīng)用程序中。
<!-- app.component.html --> <browser-faker *developmentOnly></browser-faker> <internet-explorer-11-banner></internet-explorer-11-banner>URL: <code><browser-url></browser-url></code>最后的效果:選擇 IE 11 時,出現(xiàn) deprecation 提示:
選擇其他瀏覽器時,該提示消失:
Summary
為了能夠模擬用戶環(huán)境,我們創(chuàng)建了一個在開發(fā)模式下有條件地呈現(xiàn)的瀏覽器偽造組件。 我們將瀏覽器狀態(tài)封裝在一個基于類的服務(wù)中,并讓應(yīng)用程序依賴它。 這與瀏覽器偽造者使用的服務(wù)相同。
瀏覽器偽造器是在 Angular 應(yīng)用程序中偽造依賴項的一個簡單示例。 我們討論了動態(tài)配置 Angular 依賴注入機制的其他技術(shù)。
本文提到的測試程序地址:https://stackblitz.com/edit/testing-and-faking-angular-dependencies-app?file=src%2Fapp%2Fbrowser%2Fbrowser-faker.component.html
更多Jerry的原創(chuàng)文章,盡在:“汪子熙”:
總結(jié)
以上是生活随笔為你收集整理的Angular 应用级别的依赖 Fake的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jquery的发展由来和深入理解(一)
- 下一篇: 碧蓝航线尼古拉斯攻略 尼古拉斯在哪里打捞