Guías Detalladas
Pruebas

Probar directivas de atributo

Una directiva de atributo modifica el comportamiento de un elemento, componente u otra directiva. Su nombre refleja la forma en que se aplica la directiva: como un atributo en un elemento host.

Probar la HighlightDirective

La HighlightDirective de la aplicación de muestra establece el color de fondo de un elemento basado en un color vinculado a datos o un color predeterminado (lightgray). También establece una propiedad personalizada del elemento (customProperty) a true sin otra razón que mostrar que puede hacerlo.

app/shared/highlight.directive.ts

import {Directive, ElementRef, inject, 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  bgColor = input('', {alias: 'highlight'});  private el = inject(ElementRef);  constructor() {    this.el.nativeElement.style.customProperty = true;  }  ngOnChanges() {    this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;  }}

Se usa en toda la aplicación, quizás más simplemente en el AboutComponent:

app/about/about.component.ts

import {Component} from '@angular/core';import {HighlightDirective} from '../shared/highlight.directive';import {TwainComponent} from '../twain/twain.component';@Component({  template: `    <h2 highlight="skyblue">About</h2>    <h3>Quote of the day:</h3>    <twain-quote></twain-quote>  `,  imports: [TwainComponent, HighlightDirective],})export class AboutComponent {}

Probar el uso específico de la HighlightDirective dentro del AboutComponent requiere solo las técnicas exploradas en la sección "Pruebas de componentes anidados" de Escenarios de prueba de componentes.

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');  });});

Sin embargo, probar un solo caso de uso es poco probable que explore el rango completo de las capacidades de una directiva. Encontrar y probar todos los componentes que usan la directiva es tedioso, frágil y casi tan poco probable de ofrecer cobertura completa.

Las pruebas solo de clase podrían ser útiles, pero las directivas de atributo como esta tienden a manipular el DOM. Las pruebas unitarias aisladas no tocan el DOM y, por lo tanto, no inspiran confianza en la eficacia de la directiva.

Una mejor solución es crear un componente de prueba artificial que demuestre todas las formas de aplicar la directiva.

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({  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);  });});
HighlightDirective spec en acción

ÚTIL: El caso <input> vincula la HighlightDirective al nombre de un valor de color en el cuadro de entrada. El valor inicial es la palabra "cyan" que debería ser el color de fondo del cuadro de entrada.

Aquí hay algunas pruebas de este componente:

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({  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);  });});

Algunas técnicas son dignas de mención:

  • El predicado By.directive es una excelente manera de obtener los elementos que tienen esta directiva cuando sus tipos de elemento son desconocidos

  • La pseudo-clase :not en By.css('h2:not([highlight])') ayuda a encontrar elementos <h2> que no tienen la directiva. By.css('*:not([highlight])') encuentra cualquier elemento que no tenga la directiva.

  • DebugElement.styles permite el acceso a los estilos del elemento incluso en ausencia de un navegador real, gracias a la abstracción DebugElement. Pero siéntete libre de explotar el nativeElement cuando eso parezca más fácil o más claro que la abstracción.

  • Angular agrega una directiva al injector del elemento al que se aplica. La prueba para el color predeterminado usa el injector del segundo <h2> para obtener su instancia de HighlightDirective y su defaultColor.

  • DebugElement.properties permite el acceso a la propiedad personalizada artificial que se establece por la directiva