Tests Angular : Jest, Cypress et stratégie

🏷️ Front-end 📅 14/04/2026 01:40:00 👤 Mezgani said
Angular Tests Jest Cypress Quality
Tests Angular : Jest, Cypress et stratégie

Mettez en place une stratégie de tests Angular moderne : unitaires avec Jest, E2E avec Cypress, pyramide de tests et qualité continue.

Pyramide de tests Angular

La pyramide de tests recommande de concentrer l'effort sur les tests unitaires (rapides, nombreux, isolés), de compléter avec des tests d'intégration (services + HTTP), et de limiter les tests E2E aux parcours métier critiques.

  • Unitaires (70%) — pipes, services purs, reducers NgRx, fonctions utilitaires. Exécution en millisecondes avec Jest.
  • Intégration (20%) — composants avec TestBed, mocks HTTP, stores. Exécution en secondes.
  • E2E (10%) — parcours utilisateur complets (login, achat, formulaire multi-étapes). Exécution en minutes avec Cypress.
Anti-pattern: ne pas inverser la pyramide en écrivant 80% de E2E. Les tests E2E sont lents, fragiles et difficiles à maintenir. Chaque flakiness de test E2E coûte du temps d'équipe.

Jest : configuration et premiers tests

Angular 16+ supporte Jest nativement via le builder @angular/build:jest. Pour les projets existants :

npm install --save-dev jest jest-preset-angular @types/jest

# Remplacer Karma/Jasmine dans angular.json
ng generate @angular/build:jest-builder

Configuration minimale dans jest.config.ts :

import type { Config } from 'jest';

const config: Config = {
    preset: 'jest-preset-angular',
    setupFilesAfterFramework: ['<rootDir>/setup-jest.ts'],
    testPathPattern: '.*\\.spec\\.ts$',
    collectCoverageFrom: ['src/**/*.ts', '!src/**/*.spec.ts', '!src/main.ts'],
    coverageThresholds: {
        global: { branches: 70, functions: 75, lines: 80, statements: 80 }
    }
};

export default config;
// setup-jest.ts
import 'jest-preset-angular/setup-jest';

Test d'un service simple :

// calculator.service.spec.ts
import { CalculatorService } from './calculator.service';

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

    beforeEach(() => { service = new CalculatorService(); });

    it('should add two numbers', () => {
        expect(service.add(2, 3)).toBe(5);
    });

    it('should throw on division by zero', () => {
        expect(() => service.divide(10, 0)).toThrow('Division par zéro');
    });
});

Tester des composants avec TestBed

TestBed crée un module Angular minimal pour tester un composant avec ses dépendances réelles ou mockées.

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { ArticlesComponent } from './articles.component';
import { ArticlesService } from './articles.service';

describe('ArticlesComponent', () => {
    let fixture: ComponentFixture<ArticlesComponent>;
    let httpMock: HttpTestingController;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            imports: [ArticlesComponent],
            providers: [provideHttpClientTesting()]
        }).compileComponents();

        fixture = TestBed.createComponent(ArticlesComponent);
        httpMock = TestBed.inject(HttpTestingController);
    });

    afterEach(() => httpMock.verify());

    it('should display articles after load', () => {
        fixture.detectChanges(); // déclenche ngOnInit

        const req = httpMock.expectOne('/api/articles');
        req.flush([{ id: 1, name: 'Test article' }]);

        fixture.detectChanges();
        const items = fixture.nativeElement.querySelectorAll('.article-item');
        expect(items.length).toBe(1);
    });
});

Signal + TestBed

  1. Après chaque mutation de Signal dans un test, appelle fixture.detectChanges() pour mettre à jour le DOM.
  2. Pour les composants utilisant OnPush, appelle fixture.changeDetectorRef.markForCheck() avant detectChanges().

Cypress : tests E2E des parcours critiques

Cypress s'intègre nativement avec Angular via @cypress/angular pour les tests de composants isolés, et via le runner E2E standard pour les parcours complets.

npm install --save-dev cypress @cypress/schematic
ng add @cypress/schematic

Test E2E d'un parcours de connexion :

// cypress/e2e/auth.cy.ts
describe('Authentification', () => {
    beforeEach(() => {
        cy.visit('/login');
    });

    it('should login with valid credentials', () => {
        cy.get('[data-cy="email"]').type('user@exemple.com');
        cy.get('[data-cy="password"]').type('motdepasse123');
        cy.get('[data-cy="submit"]').click();

        cy.url().should('include', '/dashboard');
        cy.get('[data-cy="user-name"]').should('contain', 'Bonjour');
    });

    it('should show error with invalid credentials', () => {
        cy.get('[data-cy="email"]').type('faux@exemple.com');
        cy.get('[data-cy="password"]').type('mauvais');
        cy.get('[data-cy="submit"]').click();

        cy.get('[data-cy="error-message"]').should('be.visible');
        cy.url().should('include', '/login');
    });
});
data-cy: utilise des attributs data-cy dédiés aux tests — jamais de sélecteurs CSS ou de classes qui peuvent changer. Cela découple les tests de l'implémentation visuelle.

Couverture de code et CI

La couverture de code mesure quelle proportion du code est exécutée par les tests. Un seuil raisonnable pour une application Angular de production est 80% de lignes couvertes.

# Lancer les tests avec rapport de couverture
ng test --coverage --watch=false

# Dans la CI (GitHub Actions)
- name: Unit tests
  run: ng test --coverage --watch=false --browsers=ChromeHeadless

- name: E2E tests
  run: npx cypress run --headless

Exemple de configuration GitHub Actions complète :

name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: ng test --coverage --watch=false
      - run: ng build
      - run: npx cypress run

Checklist stratégie de tests

  • Respecter la pyramide : 70% unitaires, 20% intégration, 10% E2E — ne pas surinvestir dans le E2E.
  • Remplacer Karma par Jest pour des tests unitaires 3 à 5 fois plus rapides.
  • Utiliser provideHttpClientTesting() + HttpTestingController pour mocker les appels HTTP dans TestBed.
  • Tagger les éléments interactifs avec data-cy dès le développement — ne pas sélectionner par classes CSS.
  • Fixer des seuils de couverture dans jest.config.ts et bloquer le merge si non atteints.
  • Lancer les tests unitaires à chaque PR et les E2E uniquement sur les branches principales pour optimiser le pipeline.
  • Éviter les setTimeout dans les tests Cypress — utiliser les aliases et les assertions should qui retraient automatiquement.
  • Tester les cas d'erreur (réseau KO, formulaire invalide, permissions refusées) autant que les cas nominaux.