属性ディレクティブは、要素、コンポーネント、または他のディレクティブの動作を変更します。 その名前は、ディレクティブが適用される方法、つまりホスト要素の属性として適用される方法を反映しています。
HighlightDirective
のテスト
サンプルアプリケーションのHighlightDirective
は、データバインドされた色またはデフォルトの色(ライトグレー)に基づいて、要素の背景色を設定します。
また、要素のカスタムプロパティ(customProperty
)をtrue
に設定しますが、これは単に設定できることを示すためです。
app/shared/highlight.directive.ts
import {Directive, ElementRef, Input, OnChanges} from '@angular/core';@Directive({standalone: true, selector: '[highlight]'})/** * Set backgroundColor for the attached element to highlight color * and set the element's customProperty to true */export class HighlightDirective implements OnChanges { defaultColor = 'rgb(211, 211, 211)'; // lightgray @Input('highlight') bgColor = ''; constructor(private el: ElementRef) { el.nativeElement.style.customProperty = true; } ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; }}
これはアプリケーション全体で使用されています。おそらく最も簡単な例はAboutComponent
です。
app/about/about.component.ts
import {Component} from '@angular/core';import {HighlightDirective} from '../shared/highlight.directive';import {TwainComponent} from '../twain/twain.component';@Component({ standalone: true, template: ` <h2 highlight="skyblue">About</h2> <h3>Quote of the day:</h3> <twain-quote></twain-quote> `, imports: [TwainComponent, HighlightDirective],})export class AboutComponent {}
AboutComponent
内でのHighlightDirective
の特定の使用をテストするには、Component testing scenariosセクションの"Nested component tests"で説明されているテクニックのみが必要です。
app/about/about.component.spec.ts
import {provideHttpClient} from '@angular/common/http';import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {UserService} from '../model';import {TwainService} from '../twain/twain.service';import {AboutComponent} from './about.component';let fixture: ComponentFixture<AboutComponent>;describe('AboutComponent (highlightDirective)', () => { beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [AboutComponent], providers: [provideHttpClient(), TwainService, UserService], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).createComponent(AboutComponent); fixture.detectChanges(); // initial binding }); it('should have skyblue <h2>', () => { const h2: HTMLElement = fixture.nativeElement.querySelector('h2'); const bgColor = h2.style.backgroundColor; expect(bgColor).toBe('skyblue'); });});
しかし、単一のユースケースをテストしても、ディレクティブの機能のすべてを調べられるとは限りません。 ディレクティブを使用するすべてのコンポーネントを見つけてテストすることは、面倒で壊れやすく、完全なカバレッジを実現する可能性もほとんどありません。
クラスのみのテストは役立つ場合がありますが、このディレクティブのような属性ディレクティブは、DOMを操作する傾向があります。 孤立したユニットテストはDOMに触れないため、ディレクティブの有効性に対する信頼を得られません。
より良い解決策は、ディレクティブのすべての適用方法を示す人工的なテストコンポーネントを作成することです。
app/shared/highlight.directive.spec.ts (TestComponent)
import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@Component({ standalone: true, template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [HighlightDirective, TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });});
HELPFUL: <input>
のケースでは、HighlightDirective
を、入力ボックス内の色の値の名前とバインドしています。
初期値は単語"cyan"であり、これは入力ボックスの背景色になります。
このコンポーネントのテストをいくつか紹介します。
app/shared/highlight.directive.spec.ts (selected tests)
import {Component, DebugElement} from '@angular/core';import {ComponentFixture, TestBed} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {HighlightDirective} from './highlight.directive';@Component({ standalone: true, template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan" />`, imports: [HighlightDirective],})class TestComponent {}describe('HighlightDirective', () => { let fixture: ComponentFixture<TestComponent>; let des: DebugElement[]; // the three elements w/ the directive let bareH2: DebugElement; // the <h2> w/o the directive beforeEach(() => { fixture = TestBed.configureTestingModule({ imports: [HighlightDirective, TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); // initial binding // all elements with an attached HighlightDirective des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); // the h2 without the HighlightDirective bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); }); // color tests it('should have three highlighted elements', () => { expect(des.length).toBe(3); }); it('should color 1st <h2> background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow'); }); it('should color 2nd <h2> background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor); }); it('should bind <input> background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).withContext('initial backgroundColor').toBe('cyan'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).withContext('changed backgroundColor').toBe('green'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined(); }); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // // customProperty tests // it('all highlighted elements should have a true customProperty', () => { // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); // expect(allTrue).toBe(true); // }); // injected directive // attached HighlightDirective can be injected it('can inject `HighlightDirective` in 1st <h2>', () => { const dir = des[0].injector.get(HighlightDirective); expect(dir).toBeTruthy(); }); it('cannot inject `HighlightDirective` in 3rd <h2>', () => { const dir = bareH2.injector.get(HighlightDirective, null); expect(dir).toBe(null); }); // DebugElement.providerTokens // attached HighlightDirective should be listed in the providerTokens it('should have `HighlightDirective` in 1st <h2> providerTokens', () => { expect(des[0].providerTokens).toContain(HighlightDirective); }); it('should not have `HighlightDirective` in 3rd <h2> providerTokens', () => { expect(bareH2.providerTokens).not.toContain(HighlightDirective); });});
いくつかのテクニックが注目に値します。
By.directive
述語は、要素の種類が不明な場合、このディレクティブを持つ要素を取得する優れた方法です。By.css('h2:not([highlight])')
の:not
擬似クラスは、ディレクティブを持っていない<h2>
要素を見つけるのに役立ちます。By.css('*:not([highlight])')
は、ディレクティブを持っていないすべての要素を見つけます。DebugElement.styles
は、DebugElement
抽象化のおかげで、実際のブラウザがなくても、要素のスタイルにアクセスできます。 しかし、抽象化よりも簡単で明確な場合は、nativeElement
を利用してください。Angularは、適用された要素のインジェクターにディレクティブを追加します。 デフォルトの色に対するテストでは、2番目の
<h2>
のインジェクターを使用して、そのHighlightDirective
インスタンスとそのdefaultColor
を取得します。DebugElement.properties
は、ディレクティブによって設定された人工的なカスタムプロパティにアクセスできます。