填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

測試屬性型指令

Testing Attribute Directives

屬性型指令會修改元素、元件或其他指令的行為。它的名字反映了該指令的應用方式:作為宿主元素的一個屬性。

An attribute directive modifies the behavior of an element, component or another directive. Its name reflects the way the directive is applied: as an attribute on a host element.

對於本測試指南中描述的範例應用,參閱範例應用範例應用

For the sample app that the testing guides describe, see thesample appsample app.

要了解本測試指南中涉及的測試特性,請參閱teststests

For the tests features in the testing guides, seeteststests.

測試 HighlightDirective

Testing the HighlightDirective

本範例應用的 HighlightDirective 會根據資料繫結中的顏色或預設顏色(淺灰)來設定元素的背景色。它還會把該元素的自訂屬性(customProperty)設定為 true ,當然這除了示範本技術之外別無它用。

The sample application's HighlightDirective sets the background color of an element based on either a data bound color or a default color (lightgray). It also sets a custom property of the element (customProperty) to true for no reason other than to show that it can.

import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; @Directive({ 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: string; constructor(private el: ElementRef) { el.nativeElement.style.customProperty = true; } ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; } }
app/shared/highlight.directive.ts
      
      import { Directive, ElementRef, Input, OnChanges } from '@angular/core';

@Directive({ 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: string;

  constructor(private el: ElementRef) {
    el.nativeElement.style.customProperty = true;
  }

  ngOnChanges() {
    this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
  }
}
    

它在整個應用中都用到過,也許最簡單的是在 AboutComponent 中:

It's used throughout the application, perhaps most simply in the AboutComponent:

import { Component } from '@angular/core'; @Component({ template: ` <h2 highlight="skyblue">About</h2> <h3>Quote of the day:</h3> <twain-quote></twain-quote> ` }) export class AboutComponent { }
app/about/about.component.ts
      
      import { Component } from '@angular/core';
@Component({
  template: `
  <h2 highlight="skyblue">About</h2>
  <h3>Quote of the day:</h3>
  <twain-quote></twain-quote>
  `
})
export class AboutComponent { }
    

要想在 AboutComponent 中測試 HighlightDirective 的特定用法,只需要瀏覽元件測試場景中的“巢狀元件測試”一節中提到的各種技巧。

Testing the specific use of the HighlightDirective within the AboutComponent requires only the techniques explored in the "Nested component tests" section of Component testing scenarios.

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ AboutComponent, HighlightDirective ], schemas: [ NO_ERRORS_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'); });
app/about/about.component.spec.ts
      
      beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ AboutComponent, HighlightDirective ],
    schemas:      [ NO_ERRORS_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');
});
    

但是,測試單個用例不太可能涉及指令的全部能力。要找到並測試那些使用了該指令的所有元件會很乏味、很脆弱,而且幾乎不可能做到完全覆蓋。

However, testing a single use case is unlikely to explore the full range of a directive's capabilities. Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.

純類別測試可能會有一點幫助,但像這種屬性型指令往往會操縱 DOM。孤立的單元測試不會觸及 DOM,因此也無法給人帶來對指令功效的信心。

Class-only tests might be helpful, but attribute directives like this one tend to manipulate the DOM. Isolated unit tests don't touch the DOM and, therefore, do not inspire confidence in the directive's efficacy.

更好的解決方案是建立一個人工測試元件來示範應用該指令的所有方法。

A better solution is to create an artificial test component that demonstrates all ways to apply the directive.

@Component({ template: ` <h2 highlight="yellow">Something Yellow</h2> <h2 highlight>The Default (Gray)</h2> <h2>No Highlight</h2> <input #box [highlight]="box.value" value="cyan"/>` }) class TestComponent { }
app/shared/highlight.directive.spec.ts (TestComponent)
      
      @Component({
  template: `
  <h2 highlight="yellow">Something Yellow</h2>
  <h2 highlight>The Default (Gray)</h2>
  <h2>No Highlight</h2>
  <input #box [highlight]="box.value" value="cyan"/>`
})
class TestComponent { }
    

這個 <input> 用例將 HighlightDirective 繫結到輸入框中顏色值的名稱。初始值是單詞“cyan”,應該把它設為輸入框的背景顏色。

The <input> case binds the HighlightDirective to the name of a color value in the input box. The initial value is the word "cyan" which should be the background color of the input box.

下面是對該元件的一些測試:

Here are some tests of this component:

beforeEach(() => { fixture = TestBed.configureTestingModule({ declarations: [ 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).toBe('cyan', 'initial backgroundColor'); input.value = 'green'; // Dispatch a DOM event so that Angular responds to the input value change. // In older browsers, such as IE, you might need a CustomEvent instead. See // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill input.dispatchEvent(new Event('input')); fixture.detectChanges(); expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor'); }); it('bare <h2> should not have a customProperty', () => { expect(bareH2.properties.customProperty).toBeUndefined(); });
app/shared/highlight.directive.spec.ts (selected tests)
      
      beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ 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).toBe('cyan', 'initial backgroundColor');

  input.value = 'green';

  // Dispatch a DOM event so that Angular responds to the input value change.
  // In older browsers, such as IE, you might need a CustomEvent instead. See
  // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
  input.dispatchEvent(new Event('input'));
  fixture.detectChanges();

  expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');
});


it('bare <h2> should not have a customProperty', () => {
  expect(bareH2.properties.customProperty).toBeUndefined();
});
    

一些技巧值得注意:

A few techniques are noteworthy:

  • By.directive 謂詞是一種獲取那些不知道型別但都附有本指令的元素的好辦法。

    The By.directive predicate is a great way to get the elements that have this directive when their element types are unknown.

  • By.css('h2:not([highlight])') 中的 :not 偽類別可以幫助你找到那些沒有該指令的 <h2> 元素。By.css('*:not([highlight])') 可以找到沒有該指令的任意元素。

    The :not pseudo-class in By.css('h2:not([highlight])') helps find <h2> elements that do not have the directive. By.css('*:not([highlight])') finds any element that does not have the directive.

  • DebugElement.styles 提供了對元素樣式的訪問,即使沒有真正的瀏覽器也是如此,這要歸功於 DebugElement 提供的抽象。但是,如果 nativeElement 顯得比使用其抽象版本更容易或更清晰,那就把它暴露出來。

    DebugElement.styles affords access to element styles even in the absence of a real browser, thanks to the DebugElement abstraction. But feel free to exploit the nativeElement when that seems easier or more clear than the abstraction.

  • Angular 會在指令宿主元素的注入器中新增上該指令。對預設顏色的測試使用第二個 <h2> 上的注入器來獲取它的 HighlightDirective 實例及其 defaultColor

    Angular adds a directive to the injector of the element to which it is applied. The test for the default color uses the injector of the second <h2> to get its HighlightDirective instance and its defaultColor.

  • DebugElement.properties 允許訪問本指令設定的自訂屬性。

    DebugElement.properties affords access to the artificial custom property that is set by the directive.