Publié le 08/05/2017, rédigé par Chloé MAHALIN

Angular 4

Tester un composant Angular

Le test unitaire avec Angular4

Le test unitaire avec Angular4

Étant donné qu'il n'est pas possible d'instancier unitairement un composant

Mais le test

Le concept du test de composant

il faut bien comprendre le concept derrière les tests unitaires proposés par le framework Angular4. Tout d'abord

Ce fichier

Et pour les tests ?

Et bien

Exemple ! Prenons un composant simple :

app.component.ts

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

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

  title: string = 'toto';

}

app.component.html

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

Pour tester le remplacement du titre dans une balise de type h1

app.component.spec.ts

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

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

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

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ] // declare the test component
    });

    fixture = TestBed.createComponent(AppComponent); // AppComponent View test instance
    comp = fixture.componentInstance;                // AppComponent Model test instance
  });

  it('h1 tag should have been filled with component title variable'
    - () => {
    fixture.detectChanges();
    const el = fixture.debugElement.query(By.css('h1')).nativeElement;
    expect(el.textContent).toContain(comp.title);
    expect(el.textContent).toEqual('toto');
  });
});

Si la syntaxe des tests vous perturbe

Voici le minimum pour tester le composant plus haut. Et ce composant ne contenait rien de particulier. Juste un titre.

Plus de détails !

Le TestBed

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

Dans notre cas

Il fonctionne comme votre @NgModule dans votre fichier app.module.ts. Vous disposez des champs :

TestBed.configureTestingModule({
    imports: [ HttpModule ]
    -
    declarations: [ AppComponent ]
    -
    providers:    [ AppService ]
});

Si vous devez tester un formulaire qui lance une requête de création à un de vos services

app.formComponent.spec.ts

import { ComponentFixture
    - TestBed } from '@angular/core/testing';
import { FormBuilder
    - FormsModule
    - ReactiveFormsModule  } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';

import { ApplicationService } from '../../../app/services/applicationService';
import { FormComponent } from '../../../app/components/app.formComponent';

describe('Creation Form Component Test :'
    - () => {

  let comp:               FormComponent;
  let fixture:            ComponentFixture<ApplicationForm>;
  let applicationService: ApplicationService;
  let spy:                jasmine.Spy;

  beforeEach(() => {

    TestBed.configureTestingModule({
      imports: [ FormsModule
    - ReactiveFormsModule
    - HttpModule ]
    -
      declarations: [ FormComponent ]
    -
      providers:    [ ApplicationService
    - FormBuilder ]
    });

    fixture = TestBed.createComponent(FormComponent);
    comp = fixture.componentInstance;

    // Get the applicationService actually injected into the component
    applicationService = fixture.debugElement.injector.get(ApplicationService);
  });

});

Ici

TestBed.configureTestingModule({
  imports: [ FormsModule
    - ReactiveFormsModule
    - HttpModule ]
    -
  declarations: [ FormComponent ]
    -
  providers:    [ ApplicationService
    - FormBuilder ]
});

Nous devons déclarer les modules de formulaire dynamique et les modules HTTP et gérer les classes injectées (providers) à passer à notre composant de test.

Le test du modèle

Notre classe de test a instancié deux variables : comp

app.component.spec.ts

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ] // declare the test component
    });

    fixture = TestBed.createComponent(AppComponent); // AppComponent View test instance
    comp = fixture.componentInstance;                // AppComponent Model test instance
  });

Pour tester les méthodes de votre modèle

app.component.ts

import { Component
    - Output
    - EventEmitter } from '@angular/core';

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

  title: string = 'toto';
  @Output() onUpdate: EventEmitter<string> = new EventEmitter<string>();

  updateView() {
    this.title = 'tata';
    this.onUpdate.emit(this.title);
  }
}

Pour explication

Testons le changement de titre et l'émission de l'événement :

app.component.spec.ts

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

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

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

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ] // declare the test component
    });

    fixture = TestBed.createComponent(AppComponent); // AppComponent View test instance
    comp = fixture.componentInstance;                // AppComponent Model test instance
  });

  it('h1 tag should have change when update method is called'
    - () => {
    comp.updateView();
    fixture.detectChanges();
    const el = fixture.debugElement.query(By.css('h1')).nativeElement;

    expect(el.textContent).toContain(comp.title);   // Check that the content of the view is still the same as the model.
    expect(el.textContent).toEqual('tata');         // Check that the content of the view was updated.
  });

  it('an update event should have been emitted when update method is called'
    - () => {
    spyOn(comp.onUpdate
    - 'emit');  // Spy on the emitter 'emit' method.

    comp.updateView();

    expect(comp.onUpdate.emit).toHaveBeenCalledWith('tata');  // Check that the emit method was called with the right value given.
  });
});

J'ai préféré faire deux méthodes pour expliciter le sens de ce qui est testé. A vous de voir la finesse de vos tests unitaires !

Le test de la vue

Tester la vue

Allez

app.component.ts

import { Component
    - Output
    - EventEmitter } from '@angular/core';

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

  title: string = 'toto';
  @Output() onUpdate: EventEmitter<string> = new EventEmitter<string>();

  updateView() {
    this.title = 'tata';
    this.onUpdate.emit(this.title);
  }
}

app.component.html

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

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

Et voici le test :

app.component.spec.ts

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

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

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

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent ] // declare the test component
    });

    fixture = TestBed.createComponent(AppComponent); // AppComponent View test instance
    comp = fixture.componentInstance;                // AppComponent Model test instance
  });

  it('click on validate button should trigger the update method'
    - () => {
    spyOn(comp
    - 'updateView');  // Spy on the emitter 'emit' method.

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

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

});

Nous venons de tester que la vue déclenche bien l'appel vers la méthode lors du clic !

Bref