Dans ce chapitre, qui se veut plus court que le précédent, nous allons tester une directive structurelle. Késako 🤨 ? Les directives structurelles sont utilisées pour ajouter et/ou supprimer des éléments du DOM – pour modifier la structure de la page par exemple. Angular inclut quelques directives structurelles prêtes à l’emploi, comme ngIf et ngShow.

Plan global du cours

Markup

Retrouvez le code de cet article sur ce dépôt stackblitz :

Open in StackBlitz

Ou suivez pas à pas les instructions 😇 !

Tester une directive structurelle

Pour tester une directive d’attribut, c’est au final assez simple : il faut récupérer une instance de la directive, effectuer une action, puis vérifier que les modifications attendues sont bien répercutées dans le DOM. Mais avant cela, examinons de plus près la directive Unless que nous allons tester.

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
  private hasView = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }

  @Input() set appUnless(condition: boolean) {
    if (!condition && !this.hasView) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }
}

Snippet 6.1 – unless.directive.ts.

La directive s’utilise de la façon suivante, sur un élément :

<p *appUnless="condition">Montre cette phrase sauf si la condition est vraie</p>

Vous l’avez remarqué ? Je veux parler de l’astérisque 🙃. L’astérisque transforme l’élément auquel la directive est attachée en modèle. La directive contrôle ensuite la façon dont ce modèle est rendu au DOM, c’est-à-dire comment il peut modifier la structure de la page. Consultez la doc Angular sur le sujet pour en savoir plus.

Tester la directive Unless

Répertorier les cas possibles

Pour arriver à nos fins, il me semble judicieux de lister toutes les configurations possible de la directive, ce qui permettra d’en extraire plus facilement les « tests cases ». 

Configuration de test (« test case »)Affichage
L’élément doit être présent dans le DOM si la condition est « false ».Le texte est visible.
L’élément doit ne doit pas être présent dans le DOM si la condition est « true ».Le texte est invisible.
Tableau 6.1 – Deux « tests cases » pour la directive Unless.

Mise en place de la suite de tests

Maintenant que nous avons une bonne vision d’ensemble des « tests cases », nous pouvons créer la suite de tests.
Pour cela, créons d’abord un fichier nommé unless.directive.spec.ts dans le même répertoire que la classe de la directive. Comme pour la directive d’attribut, on aura besoin d’un composant « support » pour les tests :

import { Component } from "@angular/core";  ①
import { ComponentFixture, TestBed, TestModuleMetadata } from "@angular/core/testing";

import { FavIconDirective } from './fav-icon.directive';  ②

@Component({  ③
  selector: 'app-test',
  template: `  ④
    <div>
      <p *appUnless="true" id="am-i-there">Ce texte n'existe pas !</p>
      <p *appUnless="false" id="that-is-the-question">Mais celui-ci oui</p>
    </div>
  `,
  styles: []
})
class TestComponent {
}

Snippet 6.2 – Préparation du composant support pour le test.

Le composant de test est à définir directement dans le fichier unless.directive.spec.ts, cela montre bien notre intention d’avoir recours à un composant support. Récapitulons légèrement :

  • ① → import des dépendances du framework de tests d’Angular.
  • ② → import de la directive à tester.
  • ③ → définition du composant « support ».
  • ④ → le template embarque les 2 cas que nous avons identifiés plus haut.

Bien ajoutons la suite du test, c’est à dire le paramétrage du module de test du framework de test d’Angular :

// -- import section. Voir snippet 6.2.

describe('UnlessDirective', () => {  ①
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(async () => {
    const testModuleMetadata: TestModuleMetadata = {  ②
      declarations: [UnlessDirective, TestComponent]
    };
    await TestBed.configureTestingModule(testModuleMetadata).compileComponents();  ③

    fixture = TestBed.createComponent(TestComponent);  ④
    component = fixture.componentInstance;
    fixture.detectChanges();  ⑤
  });

  it('should not render the element if condition is true', () => {  ⑥

  });

  it('should render the element if condition is false', () => {  ⑥

  });
});

On en a aussi profité pour mettre en place les deux petits tests unitaires qui seront nécessaires pour cette directive. Les pauvres, ils se sentiront bien seuls une fois qu’on ne sera plus là 😌.

Comme à l’accoutumée, essayons d’y voir plus clair :

  • ① → déclaration de la suite de tests
  • ② → déclaration des options du module de test dans une variable. On apprend au passage que le type requis est TestModuleMetadata.
  • ③ → création du module de test.
  • ④ → création de la fixture qui permettra de « jouer » avec le composant de test.
  • ⑤ → initier le mécanisme « Change Detection » d’Angular.
  • ⑥ → déclaration des 2 tests unitaires.

Nous en arrivons donc au point crucial : comment vérifier qu’un élément soit bien rendu dans le DOM ? Très simple : nous allons le sélectionner avec un sélecteur css et tester son contenu. Bien, mais pourtant j’avais précisé en introduction de ce document qu’on évite au maximum de requêter le DOM car cela fragilise les tests. Oui c’est vrai, mais là on va requêter le composant de test, on peut donc s’en donner à coeur joie 😇. Ce qui donne pour les deux tests :

it('should not render the element if condition is true', () => {
  const p: HTMLElement = fixture.nativeElement.querySelector('#am-i-there');  ①
  expect(p).toBeNull();  ②
});

it('should render the element if condition is false', () => {
  const p: HTMLElement = fixture.nativeElement.querySelector('#that-is-the-question');  ③
  expect(p).not.toBeNull();  ④
  expect(p.innerText).toEqual('Mais celui-ci oui');  ⑤
});
  • → sélection de l’élément avec l’id am-i-there.
  • → nous vérifions qu’il est null.
  • → sélection de l’élément avec l’id that-is-the-question.
  • → nous vérifions qu’il n’est pas null.
  • → nous vérifions aussi le contenu de l’élément.

Parfait ! Je l’avoue volontiers, j’ai connu bien plus harassant comme suite de tests ! Juste pour le plaisir des yeux, vérifions que tout fonctionne comme attendu :

Fig 6.1 – Résultat de la suite de tests.

Ce que nous avons appris

  • Angular autorise trois types de directives : les composants, les directives d’attribut et les directives structurelles. Elles sont toutess similaires en ce sens qu’elles encapsulent des fonctionnalités réutilisables. La différence entre les composants et les directives d’attribut et structurelles est que les composants possèdent une vue.
  • Vous pouvez utiliser des directives d’attribut pour modifier l’apparence d’un élément, tandis que vous utiliserez des directives structurelles pour ajouter et supprimer des éléments du DOM.
  • Tester des directives structurelles et d’attribut est similaire en ce sens que vous définissez l’état initial d’un élément, effectuez l’action souhaitée, puis testez pour confirmer que le changement attendu se produit.
  • La méthode configureTestingModule prend un objet qui doit utiliser l’interface TestModuleMetadata. Vous pouvez soit créer une variable qui définit le type sur TestModuleMetadata, puis transmettre la variable à la méthode configureTestingModule, soit créer un objet avec des données de configuration pertinentes, puis la transmettre à la méthode configureTestingModule.