|
Publié le par Chloé MAHALIN

Angular 4

Tester un composant HTML input dans un composant Angular

Prérequis, ajouter un input à une vue.

Objectif

Angular4 est un framework web qui n'exclue pas le test unitaire. Un code maintenable et durable passe par la mise en place de tests unitaires et d'intégration. D'autant plus que Karma et Jasmine.js, déjà intégrés au build angular-cli, permettent les deux pratiques (TDD, BDD).

Mais encore faut-il savoir gérer l'événementiel des composants HTML dans les TU avec Angular.

Ici, nous allons voir comment déclencher un événement depuis un input et comment vérifier le contenu d'un input.

Préparons notre test

Créons un composant simple :

myComponent.html

<div>
    <input type="text" maxlength="20" placeholder="Type your text here" [(ngModel)]="text" />
    {{text}}
</div>

myComponent.ts

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

@Component({
  selector: 'myComponent-tag',
  templateUrl: './myComponent.html',
  styleUrls: ['./myComponent.css']
})
export class MyComponent {

  text: string;
}

Je vous épargne la rédaction du css, puisque le contenu sera vide.

Pour résumer ! Nous disposons dans la vue d'un input dont la valeur est liée à la variable 'text' du model de mon composant. Lorsque l'input sera mis à jour, le contenu de ma variable le sera aussi. Cela devrait se constater immédiatement puisque l'on affiche le contenu de la variable text directement dans la vue à travers la ligne :

{{text}}

Créons la classe de test associée :

myComponent.spec.ts

import { ComponentFixture, TestBed, tick, fakeAsync, async } from '@angular/core/testing';
import { FormBuilder, FormsModule, ReactiveFormsModule  } from '@angular/forms';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { MyComponent } from './myComponent';

describe('Input testing :', () => {

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ FormsModule, ReactiveFormsModule ],
      declarations: [ MyComponent ],
      providers:    [ FormBuilder ]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    comp = fixture.componentInstance;
  });

});

On peut constater que nous allons évaluer dans un environnement de test simulé asynchrone. Ceci afin de permettre les mises à jour depuis et vers la vue. Nous allons ajouter des tests à cette classe.

Vérifier la valeur contenue dans un input

C'est le plus simple.

myComponent.spec.ts

  it('the input value should be updated with the variable value', () => {
    // On met à jour la variable dans le modèle
    comp.text = 'ceci est un test';
    // On détecte les modifications depuis le modèle.
    fixture.detectChanges();

    // On récupère l'input dans la vue
    const input = fixture.debugElement.query(By.css('input')).nativeElement;

    // On vérifie la valeur de l'input
    expect(input.value).toEqual('ceci est un test');
  });

Dans cet exemple, on met à jour la variable directement depuis le modèle, puis on vérifie que le contenu de l'input a bien été mis à jour avec cette valeur. Le test est assez facile dans ce sens.

Faisons maintenant le test inverse :

Déclencher la mise à jour du modèle depuis l'input

Maintenant, nous allons vérifier que le modèle se met à jour depuis l'input dans la vue.

myComponent.spec.ts

  it('the variable should be updated with the input', fakeAsync(() => {
    // On récupère l'input dans la vue
    const input = fixture.debugElement.query(By.css('input')).nativeElement;

    // On rajoute du contenu dans l'input
    input.value = 'ceci est un autre test';
    input.dispatchEvent(new Event('input'));
    tick();
    fixture.detectChanges();

    // On vérifie la valeur de la variable
    expect(comp.text).toEqual('ceci est un autre test');
  }));

Pour faire ce test, nous avons du positionner notre contexte dans une fake Asynchrone zone par l'utilisation de la déclaration fakeAsync. Pour plus d'informations sur async, fakeAsync ou tick, voir l'article qui est consacré au sujet du test asynchrone.

Nous avons pu tester que le model est mis à jour depuis la vue. Maintenant, supposons que je souhaite vérifier l'appel à une méthode depuis un événement sur le input.

Simuler un évènement sur un input

Reprenons notre composant et rajoutons-lui un peu de contenu :

myComponent.html

<div>
    <input type="text" maxlength="20" placeholder="Type your text here" [(ngModel)]="text" (keyup)="checkInputIsEmpty()" />
    {{text}} {{ textIsEmpty ? ' is empty' : ' is not empty' }}
</div>

myComponent.ts

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

@Component({
  selector: 'myComponent-tag',
  templateUrl: './myComponent.html',
  styleUrls: ['./myComponent.css']
})
export class MyComponent {

  text: string = '';
  textIsEmpty: boolean = true;

  checkInputIsEmpty(): void {
    this.textIsEmpty = this.text.length == 0;
  }
}

Nous avons rajouté une méthode qui se déclenchera à chaque fois d'une touche de clavier est actionnée au moment ou la touche remonte (fin du contact touche). Elle vérifie que le contenu de l'input est vide ou non.

Maintenant, écrivons le test qui correspond. Logiquement, il s'agit de déclencher un événement keyup depuis l'input.

myComponent.spec.ts

  it('the method should be triggered with the keyup event on the input', fakeAsync(() => {
    // spyOn(comp, 'checkInputIsEmpty'); <--- you can also spy on the method to check the triggering.

    // On récupère l'input dans la vue
    const input = fixture.debugElement.query(By.css('input')).nativeElement;

    // On rajoute du contenu dans l'input
    input.value = 'triggered';
    input.dispatchEvent(new Event('input'));
    // Voici l'évènement keyup qui va déclencher l'appel à la méthode.
    input.dispatchEvent(new Event('keyup'));
    tick();
    fixture.detectChanges();

    // On vérifie la valeur de la variable
    expect(comp.textIsEmpty).toBeFalsy();
  }));