填写这份《一分钟调查》,帮我们(开发组)做得更好!去填写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.