Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🧪
Intermediaire 25 min Angular

Tester Angular avec Jest

Pourquoi Tester Angular avec Jest ?

Tester Angular avec Jest est essentiel pour les développeurs front-end car il permet d'assurer la qualité et la fiabilité de l'application Angular tout au long du développement. Dans un contexte réel, tester régulièrement une application Angular aide à identifier les bugs tôt dans le processus, ce qui peut économiser beaucoup de temps et d'efforts lors des phases ultérieures de développement ou de production.

Un cas d'utilisation concret est la gestion des formulaires. Tester un formulaire Angular avec Jest permet de s'assurer que chaque champ fonctionne correctement, que les validations sont appliquées en temps réel et que le comportement du formulaire sous différents scénarios est conforme aux attentes.

Prerequis

Voici la liste des connaissances nécessaires et des outils à installer :

  • Connaissances requises :

    • Connaissance avancée d'Angular
    • Compréhension de Jest, un framework de tests JavaScript
    • Familiarité avec TypeScript
  • Outils à installer :

    • Node.js (version recommandée : 14.x ou ultérieure)
    • Angular CLI (version recommandée : 12.x ou ultérieure)

Pour installer les outils nécessaires, exécutez les commandes suivantes dans votre terminal :

## Installer Node.js
https://nodejs.org/en/download/

## Installer Angular CLI
npm install -g @angular/cli

Concepts fondamentaux

1. Jest et Angular CLI

Jest est un framework de tests JavaScript populaire qui peut être utilisé avec Angular pour écrire des tests unitaires, d'intégration et de bout à bout.

Angular CLI fournit des outils pour générer les fichiers de test automatiquement. Voici comment créer un nouveau projet Angular :

ng new angular-jest-project
cd angular-jest-project

2. Tests Unitaires avec Jest

Les tests unitaires vérifient le comportement individuel d'un composant, d'un service ou d'une fonction.

Créons un test unitaire pour un simple service :

// src/app/data.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';

describe('DataService', () => {
  let service: DataService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(DataService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return data', () => {
    const expectedData = { message: 'Hello, World!' };
    spyOn(service, 'getData').and.returnValue(expectedData);

    const result = service.getData();

    expect(result).toEqual(expectedData);
  });
});

3. Tests d'Intégration avec Jest

Les tests d'intégration vérifient comment les composants et les services s'interconnectent.

Créons un test d'intégration pour un composant qui utilise un service :

// src/app/data.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DataComponent } from './data.component';
import { DataService } from '../data.service';

describe('DataComponent', () => {
  let component: DataComponent;
  let fixture: ComponentFixture<DataComponent>;
  let dataService: DataService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ DataComponent ],
      providers: [ DataService ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(DataComponent);
    component = fixture.componentInstance;
    dataService = TestBed.inject(DataService);
    fixture.detectChanges();
  });

  it('should display message from service', () => {
    const expectedData = { message: 'Hello, World!' };
    spyOn(dataService, 'getData').and.returnValue(expectedData);

    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.textContent).toContain('Hello, World!');
  });
});

4. Tests d'End-to-End avec Jest

Les tests d'end-to-end vérifient le comportement complet de l'application dans un environnement réel.

Créons un test E2E pour une route simple :

// src/app/app.e2e-spec.ts
import { AppPage } from './app.po';

describe('angular-jest-project', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display welcome message', async () => {
    await page.navigateTo();
    expect(await page.getTitleText()).toEqual('Welcome to angular-jest-project!');
  });
});

Mise en pratique : projet fil rouge

Commençons par créer un gestionnaire de tâches simple avec Angular et Jest.

Étape 1 : Créer le Projet

ng new task-manager
cd task-manager
npm install --save-dev jest @types/jest ts-jest @angular-builders/jest

Étape 2 : Générer les Composants et Services

ng generate component tasks
ng generate service task

Étape 3 : Modifier le Service task.service.ts

// src/app/task.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  private tasks = [
    { id: 1, title: 'Task 1', completed: false },
    { id: 2, title: 'Task 2', completed: true }
  ];

  getTasks() {
    return this.tasks;
  }

  addTask(task) {
    this.tasks.push(task);
  }

  completeTask(id) {
    const task = this.tasks.find(t => t.id === id);
    if (task) {
      task.completed = true;
    }
  }
}

Étape 4 : Modifier le Composant tasks.component.ts

// src/app/tasks/tasks.component.ts
import { Component, OnInit } from '@angular/core';
import { TaskService } from '../task.service';

@Component({
  selector: 'app-tasks',
  templateUrl: './tasks.component.html',
  styleUrls: ['./tasks.component.css']
})
export class TasksComponent implements OnInit {
  tasks = [];

  constructor(private taskService: TaskService) {}

  ngOnInit(): void {
    this.tasks = this.taskService.getTasks();
  }

  addTask(task) {
    this.taskService.addTask(task);
    this.tasks = this.taskService.getTasks();
  }

  completeTask(id) {
    this.taskService.completeTask(id);
    this.tasks = this.taskService.getTasks();
  }
}

Étape 5 : Créer les Tests Unitaires pour le Service

// src/app/task.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { TaskService } from './task.service';

describe('TaskService', () => {
  let service: TaskService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(TaskService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return tasks', () => {
    const expectedTasks = [
      { id: 1, title: 'Task 1', completed: false },
      { id: 2, title: 'Task 2', completed: true }
    ];
    expect(service.getTasks()).toEqual(expectedTasks);
  });

  it('should add a task', () => {
    const newTask = { id: 3, title: 'New Task', completed: false };
    service.addTask(newTask);
    expect(service.getTasks().length).toBe(3);
  });

  it('should complete a task', () => {
    const taskId = 1;
    service.completeTask(taskId);
    expect(service.getTasks()[0].completed).toBe(true);
  });
});

Étape 6 : Créer les Tests d'Intégration pour le Composant

// src/app/tasks/tasks.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TasksComponent } from './tasks.component';
import { TaskService } from '../task.service';

describe('TasksComponent', () => {
  let component: TasksComponent;
  let fixture: ComponentFixture<TasksComponent>;
  let taskService: TaskService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ TasksComponent ],
      providers: [ TaskService ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(TasksComponent);
    component = fixture.componentInstance;
    taskService = TestBed.inject(TaskService);
    fixture.detectChanges();
  });

  it('should display tasks', () => {
    const expectedTasks = [
      { id: 1, title: 'Task 1', completed: false },
      { id: 2, title: 'Task 2', completed: true }
    ];
    spyOn(taskService, 'getTasks').and.returnValue(expectedTasks);

    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expectedTasks.forEach(task => {
      expect(compiled.querySelector(`[data-task-id="${task.id}"]`).textContent).toContain(task.title);
    });
  });

  it('should add a task', () => {
    const newTask = { id: 3, title: 'New Task', completed: false };
    const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
    inputElement.value = newTask.title;
    inputElement.dispatchEvent(new Event('input'));

    const addButton = fixture.debugElement.query(By.css('.add-task-button'));
    addButton.triggerEventHandler('click', null);

    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector(`[data-task-id="${newTask.id}"]`).textContent).toContain(newTask.title);
  });

  it('should complete a task', () => {
    const taskId = 1;
    const checkboxElement = fixture.debugElement.query(By.css(`input[data-task-id="${taskId}"]`)).nativeElement;
    checkboxElement.checked = true;
    checkboxElement.dispatchEvent(new Event('change'));

    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector(`[data-task-id="${taskId}"]`).textContent).toContain('(completed)');
  });
});

Étape 7 : Créer les Tests E2E

// src/app/app.e2e-spec.ts
import { AppPage } from './app.po';

describe('task-manager', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display title', async () => {
    await page.navigateTo();
    expect(await page.getTitleText()).toEqual('Task Manager');
  });

  it('should add a task', async () => {
    const newTask = 'New Task';
    await page.addTask(newTask);
    expect(await page.getTaskCount()).toBe(1);
    expect(await page.getTaskTitle(0)).toContain(newTask);
  });

  it('should complete a task', async () => {
    await page.completeTask(0);
    expect(await page.getTaskStatus(0)).toBe('(completed)');
  });
});

Étape 8 : Exécuter les Tests

ng test --watch=false
ng e2e --watch=false

Erreurs fréquentes et debugging

1. TypeError: Cannot read property 'test' of undefined

Cause : Jest n'est pas correctement configuré dans le projet Angular.

Correction :

ng test --watch=false
ng e2e --watch=false

2. TypeError: Cannot read property 'getTasks' of undefined

Cause : Le service n'est pas correctement injecté dans le composant.

Correction :

// src/app/tasks/tasks.component.ts
import { Component, OnInit } from '@angular/core';
import { TaskService } from '../task.service';

@Component({
  selector: 'app-tasks',
  templateUrl: './tasks.component.html',
  styleUrls: ['./tasks.component.css']
})
export class TasksComponent implements OnInit {
  tasks = [];

  constructor(private taskService: TaskService) {}

  ngOnInit(): void {
    this.tasks = this.taskService.getTasks();
  }

  addTask(task) {
    this.taskService.addTask(task);
    this.tasks = this.taskService.getTasks();
  }

  completeTask(id) {
    this.taskService.completeTask(id);
    this.tasks = this.taskService.getTasks();
  }
}

3. TypeError: Cannot read property 'click' of null

Cause : L'élément à cliquer n'existe pas dans le DOM.

Correction :

// src/app/tasks/tasks.component.spec.ts
it('should complete a task', () => {
  const taskId = 1;
  const checkboxElement = fixture.debugElement.query(By.css(`input[data-task-id="${taskId}"]`)).nativeElement;
  checkboxElement.checked = true;
  checkboxElement.dispatchEvent(new Event('change'));

  fixture.detectChanges();
  const compiled = fixture.nativeElement as HTMLElement;
  expect(compiled.querySelector(`[data-task-id="${taskId}"]`).textContent).toContain('(completed)');
});

Pour aller plus loin

  1. Tests Asynchrones : Apprendre à tester des fonctions asynchrones avec Jest.

  2. Code Coverage : Configurer la mesure de couverture de code pour vous assurer que vos tests touchent bien tout le code.

  3. Mocking Services : Apprendre à utiliser des mocks avec Jest pour isoler les composants et services.

Défi pratique

Créez un simple application Angular qui permet d'afficher une liste de livres, d'ajouter de nouveaux livres et de chercher des livres par titre. Assurez-vous de couvrir tous les cas d'utilisation avec des tests unitaires, d'intégration et d'end-to-end.

Besoin d'aide sur Angular ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Quel est le principe de base du fonctionnement de Jest avec Angular?
Jest est un framework de test JavaScript utilisé pour tester des applications Angular. Il permet d'écrire des tests unitaires, d'intégration et e2e (end-to-end) pour les composants, services et autres éléments de l'application Angular.
Comment configurer Jest dans un projet Angular?
Pour configurer Jest dans un projet Angular, vous devez installer Jest via npm avec la commande 'npm install --save-dev jest'. Ensuite, ajoutez les configurations nécessaires au fichier 'karma.conf.js' et créez un fichier de configuration Jest spécifique pour votre application.
Quelle est l'utilité des snapshots dans les tests Angular avec Jest?
Les snapshots permettent de capturer une image de l'état actuel d'une interface utilisateur ou d'un composant. Ils sont particulièrement utiles pour s'assurer que l'interface ne change pas involontairement lors des mises à jour du code, en vérifiant les changements par rapport à la capture précédente.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.