Publié le 07/29/2017, rédigé par Chloé MAHALIN

Angular 4

Tester un composant Angular

Mocker un composant enfant dans son parent avec Angular

Mocker un composant enfant dans un composant parent est extrèmement sensible. Il existe beaucoup de manières de faire et toutes ne sont pas respectueuse de la quantité d'éléments à instancier pour votre jeu de tests unitaires.

Voici la solution que je propose et qui respecte l'injection de composants (@ViewChild ou @ViewChildren).

Voici les deux composants qui s'imbriquent :

Le composant parent

app.component.ts

import { Component } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-component'
    -
  templateUrl: './app.component.html'
    -
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  @ViewChild (ChildComponent) childComponent: ChildComponent;

  updateView() {
    this.childComponent.setTitle('tata');
  }
}

app.component.html

<child-component></child-component>
<button (click)="updateView();">Validate</button>

Le composant enfant

child.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'child-component'
    -
  templateUrl: './child.component.html'
    -
  styleUrls: ['./child.component.css']
})
export class ChildComponent {
  title: string = 'toto';
  setTitle(newTitle: string): void { this.title = newTitle };
}

child.component.html

<h1>{{ title }}</h1>

Préparation du test du parent vers l'enfant

Dans un premier temps

app.component.ts

import { Component } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-component'
    -
  templateUrl: './app.component.html'
    -
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  @ViewChild ('childComponent') childComponent: ChildComponent;     <== ici je définie un id propre au composant

  updateView(): void {
    this.childComponent.setTitle('tata');
  }
}

app.component.html

<child-component #childComponent></child-component>                  <== L'id dans le model doit correspondre à l'ID de la vue.
<button (click)="updateView();">Validate</button>

Pour notre application

Définissons la structure de notre fichier de test :

import { ComponentFixture
    - TestBed } from '@angular/core/testing';
import { DebugElement
    - Component } from '@angular/core';
import { By } from '@angular/platform-browser';

import { AppComponent } from '../../../app/components/app.component';

// This is the mock of our child component !
@Component({
  selector: 'child-component'
    -
  templateUrl: './app.component.html'
})
class MockChildComponent {
  updateView(): void {}
}

describe('Test Component Workflow :'
    - () => {

  let comp:                             AppComponent;
  let fixture:                          ComponentFixture<AppComponent>;

  beforeEach(() => {

    TestBed.configureTestingModule({
      declarations: [ AppComponent
    - MockChildComponent ]
    });

    fixture = TestBed.createComponent(AppComponent);
    fixture.autoDetectChanges();
    comp = fixture.componentInstance;
  });

});

Nous avons créé un mock dans notre classe de test

Test du comportement !

Voici ce que nous allons tester : si je clique sur le bouton du parent

import { ComponentFixture
    - TestBed } from '@angular/core/testing';
import { DebugElement
    - Component } from '@angular/core';
import { By } from '@angular/platform-browser';

import { AppComponent } from '../../../app/components/app.component';

// This is the mock of our child component !
@Component({
  selector: 'child-component'
    -
  templateUrl: './app.component.html'
})
class MockChildComponent {
  updateView(): void {}
}

describe('Test Component Workflow :'
    - () => {

  let comp: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(() => {

    TestBed.configureTestingModule({
      declarations: [ AppComponent
    - MockChildComponent ]
    });

    fixture = TestBed.createComponent(AppComponent);
    fixture.autoDetectChanges();
    comp = fixture.componentInstance;
  });

  it('click on validate button should call for update of child'
    - () => {
    spyOn(comp.childComponent
    - 'updateView');

    // trigger the click
    const button = fixture.debugElement.query(By.css('button')).nativeElement;
    button.dispatchEvent(new Event('click'));

    expect(comp.childComponent.updateView).toHaveBeenCalled();
  });

});

Voici une manière simple et peu coûteuse de tester la communication entre deux composants !