Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a ViewChild/ContentChild property in a angular component?

I have a form container for proper displaying validation errors. I access the form control via ContentChild decorator and manipulate it reactively for building the validation messages.

My question is: how can I proper unit test such a component?

component.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);
  }
}

I just use the status changes to update my styling. Also to display all of the validation messages in case of a fail.

component.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>

component.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();
  });
});
like image 719
Gabriel Araujo Avatar asked Oct 15 '25 12:10

Gabriel Araujo


1 Answers

In order to provide content projection to tested component, it can be compiled inside another wrapper component.

Considering that desired ContentChild directive (FormControlName) can exist without form group fixtures:

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

Directives and components should be included to test bed:

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

It's expected that the property will be an instance of FormControlName directive class, tested component can be queried and asserted:

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

Intermediary assertions can be added to taste.

like image 61
Estus Flask Avatar answered Oct 18 '25 04:10

Estus Flask