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.
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
- Après chaque mutation de Signal dans un test, appelle
fixture.detectChanges()pour mettre à jour le DOM. - Pour les composants utilisant
OnPush, appellefixture.changeDetectorRef.markForCheck()avantdetectChanges().
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 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()+HttpTestingControllerpour mocker les appels HTTP dans TestBed. - Tagger les éléments interactifs avec
data-cydès le développement — ne pas sélectionner par classes CSS. - Fixer des seuils de couverture dans
jest.config.tset 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
setTimeoutdans les tests Cypress — utiliser les aliases et les assertionsshouldqui retraient automatiquement. - Tester les cas d'erreur (réseau KO, formulaire invalide, permissions refusées) autant que les cas nominaux.