enhetstestning - Hur testar man en ViewChild/ContentChild-egenskap i en Angular-komponent?


Jag har en formulärcontainer för korrekt visning av valideringsfel. Jag kommer åt formulärkontrollen via ContentChild-dekoratören och manipulerar den reaktivt för att skapa valideringsmeddelandena.

Min fråga är: hur kan jag korrekt enhetstesta en sådan komponent?

komponent.ts

import { Component, ContentChild, Input, AfterContentInit } from '@angular/core';
import { FormControlName } from '@angular/forms';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-form-group',
  templateUrl: './form-group.component.html',
  styleUrls: ['./form-group.component.scss'],
})
export class FormGroupComponent implements AfterContentInit {

  @Input()
  errorMessages: { [key: string]: string } = { };

  @ContentChild(FormControlName)
  control: FormControlName;

  message: Observable<string>;
  success: Observable<boolean>;
  error: Observable<boolean>;

  private buildErrorMessage(status): string {
    if (status === 'INVALID' && this.control.touched && this.control.dirty) {
      return Object.keys(this.control.errors)
        .map(errorKey => this.errorMessages[errorKey])
        .join('\n');
    }
  }

  ngAfterContentInit() {
    const delayedStatusChanges = this.control.statusChanges
      .debounceTime(500);

    this.message = delayedStatusChanges
      .map(status => this.buildErrorMessage(status));
    this.success = delayedStatusChanges
      .map(status => status === 'VALID' && this.control.touched && this.control.dirty);
    this.error = delayedStatusChanges
      .map(status => status === 'INVALID' && this.control.touched && this.control.dirty);
  }
}

Jag använder bara statusändringarna för att uppdatera min stil. Också för att visa alla valideringsmeddelanden vid ett fel.

komponent.html

<div
  class="form-group"
  [class.has-success]="success | async"
  [class.has-error]="error | async"
>

  <ng-content></ng-content>

  <div
    class="help-block"
    [hidden]="message | async"
  >
    <i class="fa fa-remove"></i>
    {{ message | async }}
  </div>

</div>

komponent.spec.ts

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { FormGroupComponent } from './form-group.component';

describe('FormGroupComponent', () => {
  let component: FormGroupComponent;
  let fixture: ComponentFixture<FormGroupComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        FormGroupComponent,
      ],
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(FormGroupComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Svar #1

För att kunna tillhandahålla innehållsprojektion till den testade komponenten kan den kompileras inuti en annan omslagskomponent.

Med tanke på att den önskade ContentChild-direktiven (FormControlName) kan existera utan formulärgruppsfästen:

@Component({
  template: `<app-form-group><input [formControlName]></app-form-group>`
})
class WrapperComponent {}

Direktiv och komponenter bör inkluderas i testbädden:

TestBed.configureTestingModule({
  declarations: [FormGroupComponent, WrapperComponent, FormControlName]
})

Det förväntas att egenskapen kommer att vara en instans av FormControlName direktivklass, testad komponent kan frågas och testas:

fixture = TestBed.createComponent(WrapperComponent);
fixture.detectChanges();

const wrapperCompDE = fixture.debugElement;
const testedCompDE = wrapperCompDE.query(By.directive(FormGroupComponent));
const testedComp = testedCompDE.componentInstance;
const inputDE = testedCompDE.query(By.css('input'));
const contentChild = inputDE.injector.get(FormControlName);

expect(contentChild instanceof FormControlName).toBe(true);
expect(testedComp.control).toBe(contentChild);

Mellanliggande påståenden kan läggas till efter smak.

Relaterade artiklar