Tests en TypeScript : de l'unit à l'e2e — guide complet
\nLe développement web moderne est impensable sans des tests de qualité. TypeScript, en tant que surcouche de JavaScript, fournit des outils puissants pour le typage, mais ne résout pas le problème des erreurs logiques. Dans cet article, nous aborderons trois niveaux de tests : unit (tests unitaires), integration (tests d'intégration) et e2e (tests de bout en bout). Vous apprendrez à configurer l'environnement, à écrire des tests efficaces en TypeScript et à les intégrer dans un pipeline CI/CD.
\n\n1. Tests unitaires : la base de la pyramide des tests
\nLes tests unitaires vérifient des modules isolés (fonctions, classes, méthodes) sans dépendances externes. Pour TypeScript, le meilleur choix est Jest — il prend en charge TypeScript nativement et offre une API riche pour les mocks et les assertions.
\n\nConfiguration de Jest pour TypeScript
\nnpm install --save-dev jest @types/jest ts-jest\nnpx ts-jest config:init\nLe fichier jest.config.ts doit contenir :
export default {\n preset: 'ts-jest',\n testEnvironment: 'node',\n roots: ['<rootDir>/src'],\n testMatch: ['**/__tests__/**/*.test.ts']\n};\n\nExemple de test unitaire d'une fonction
\nSupposons que nous ayons une fonction de calcul de remise :
\n// src/utils/discount.ts\nexport function calculateDiscount(price: number, percent: number): number {\n if (percent < 0 || percent > 100) throw new Error('Invalid percent');\n return price - (price * percent) / 100;\n}\nTest pour celle-ci :
\n// src/__tests__/discount.test.ts\nimport { calculateDiscount } from '../utils/discount';\n\ndescribe('calculateDiscount', () => {\n it('doit retourner 80 pour un prix de 100 et une remise de 20%', () => {\n expect(calculateDiscount(100, 20)).toBe(80);\n });\n\n it('doit lever une erreur pour un pourcentage invalide', () => {\n expect(() => calculateDiscount(100, 150)).toThrow('Invalid percent');\n });\n\n it('doit retourner 0 pour une remise de 100%', () => {\n expect(calculateDiscount(50, 100)).toBe(0);\n });\n});\n\nTravail avec les mocks
\nPour isoler les tests, utilisez des mocks. Exemple avec un service dépendant de la base de données :
\n// src/services/user.service.ts\nimport { db } from '../database';\n\nexport class UserService {\n async getUser(id: number) {\n const user = await db.findUser(id);\n if (!user) throw new Error('User not found');\n return user;\n }\n}\n// src/__tests__/user.service.test.ts\nimport { UserService } from '../services/user.service';\nimport { db } from '../database';\n\njest.mock('../database');\n\ndescribe('UserService', () => {\n it('doit retourner un utilisateur', async () => {\n const mockUser = { id: 1, name: 'Alice' };\n (db.findUser as jest.Mock).mockResolvedValue(mockUser);\n\n const service = new UserService();\n const result = await service.getUser(1);\n expect(result).toEqual(mockUser);\n });\n});\n\n2. Tests d'intégration : vérification de l'interaction des composants
\nLes tests d'intégration vérifient comment les modules fonctionnent ensemble : routes API, bases de données, services externes. Pour les applications TypeScript sur Node.js, on utilise souvent Supertest en combinaison avec Jest.
\n\nConfiguration d'un test d'intégration pour Express/NestJS
\nnpm install --save-dev supertest @types/supertest\nExemple de test pour une API REST (NestJS) :
\n// src/__tests__/app.integration.test.ts\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { AppModule } from '../app.module';\n\ndescribe('AppController (e2e)', () => {\n let app: INestApplication;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n a