Tests unitaires sur un composant Angular utilisant @Input() et @Output()

Photo by AltumCode on Unsplash

Tests unitaires sur un composant Angular utilisant @Input() et @Output()

Nombreux sont les développeurs Angular qui ont peur d'écrire des tests unitaires ou ne savent carrément pas comment faire et par où commencer. Le but de cet article est de prendre un léger exemple afin de démystifier la complexité autour des tests unitaires. Nous utiliserons bien-sûr le combo Jasmine/Karma qui est déjà présent lors de la création d'un projet Angular.

En effet, Jasmine est un framework qui nous permet d'aller vite dans l'écriture de nos tests unitaires et Karma est un serveur qui nous permet de lancer nos tests avec un rendu de tout ce qui se passe sur notre navigateur.

Considérons un composant réutilisable HelloComponent qui permet d'afficher le nom de l'utilisateur (username) dès qu'on clique sur un bouton (Hello).

<div>
    <p> {{ username }} </p>
   <button (click)='sayHello(username)'> Hello </button>
</div>
export class HelloComponent {
    @Input() username = '';
    @Output() hello = new EventEmitter<string>()

    public sayHello(username: string): void {
        this.hello.emit(username);
    }
}

Comme nous pouvons le pouvoir, nous avons notre composant HelloComponent qui comprend :

  • username qui est notre propriété Input()

  • hello qui est notre évènement Output()

Notre composant réutilisable pourra être appelé par un composant parent comme ceci par exemple :

<app-hello [username]="'john'" (hello)="onSayHello($event)">
</app-hello>

En créant un composant Angular, nous avons généralement un fichier qui a pour extension `.spec.ts` . C'est le fichier dédié à l'écriture des tests unitaires liés à notre composant. En ouvrant le fichier hello.component.spec.ts qui fait référence à notre composant HelloComponent, nous avons ce code de base :

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

import { NotificationToastComponent } from './hello.component';

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

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

    fixture = TestBed.createComponent(HelloComponent);
    component = fixture.componentInstance;
    component.username = 'john';
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
  • Le code généré nous a créé une instance du composant HelloComponent. Ensuite, il a fait appel à la méthode detectChanges() pour mettre à jour la vue si entre temps une propriété du composant a été modifié.

  • Nous avons rajouté component.username = 'john'; afin qu'il initialise la valeur de username avec 'john' lors de la création de l'instance.

Nous pouvons à présent écrire les tests unitaires qu'il faut par rapport à notre logique métier. Nous écrirons deux tests unitaires :

1) Le premier test vise à vérifier que notre variable username est bien affichée sur notre interface :

it('should display username', () => {
  const messageElement = fixture.nativeElement.querySelector('p');
  expect(messageElement.textContent).toContain('john');
});
  • Dans notre premier test ci-dessus, nous avons sélectionné l'élément HTML qui affiche la propriété username et vérifié qu'il contient la valeur "john". La valeur de username a été initialisée dans la méthode beforeEach() .

2) Le second test vise à vérifier que l'évènement hello() est émise lorsqu'on fait appel à la méthode sayHello() :

   it('should emit hello event', () => {
    spyOn(component.hello, 'emit');
    component.sayHello('john');
    expect(component.hello.emit).toHaveBeenCalledWith('john');
  });
  • Dans notre second test ci-dessus, nous avons utilisé la méthode spyOn() pour espionner l'évènement hello(). En effet, spyOn() est une fonction qui enregistre des informations sur les appels de la méthode à espionner, comme le nombre de fois où elle a été appelée, avec quels arguments et ce qu'elle a renvoyé. Enfin, nous avons vérifié que l'événement hello() a été émis avec la valeur 'john' lorsqu'on appelle la fonction sayHello().

Nous pouvons lancer la commande ng test pour lancer le serveur Karma et voir le rendu de nos tests unitaires sur un navigateur.

Pour finir, voici le code complet lié aux tests unitaires sur notre composant HelloComponent :

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

import { NotificationToastComponent } from './hello.component';

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

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

    fixture = TestBed.createComponent(HelloComponent);
    component = fixture.componentInstance;
    component.username = 'john';
    fixture.detectChanges();
  });

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

  it('should display username', () => {
      const messageElement = fixture.nativeElement.querySelector('p');
      expect(messageElement.textContent).toContain('john');
  });

  it('should emit hello event', () => {
    spyOn(component.hello, 'emit');
    component.sayHello('john');
    expect(component.hello.emit).toHaveBeenCalledWith('john');
  });
});

En somme, nous avons exploré comment écrire des tests unitaires pour un composant Angular qui utilise les décorateurs @Input() et @Output(). Les tests unitaires sont une partie importante du processus de développement logiciel et nous espérons que nous les aborderons davantage avec plus d'aisance et moins de peur.

Pour plus d'informations :

👉 Angular Testing

👉 Jasmine Documentation

Did you find this article valuable?

Support Ezéchiel Amen AGBLA by becoming a sponsor. Any amount is appreciated!