Introduction : Pourquoi les tests en TypeScript sont un must have
TypeScript est un outil puissant qui ajoute un typage statique à JavaScript. Cependant, même le système de typage le plus strict ne garantit pas que votre code fonctionnera correctement à l'exécution. Les bugs, les erreurs logiques, le traitement incorrect des cas limites – tout cela reste en dehors du champ de la compilation. C'est là que les tests viennent à la rescousse.
Dans cet article, nous allons aborder trois niveaux principaux de tests en TypeScript : unit (modulaires), integration (d'intégration) et e2e (de bout en bout). Vous apprendrez en quoi ils diffèrent, quand les appliquer, et vous verrez des exemples de code concrets. Après avoir lu cet article, vous serez en mesure de configurer un pipeline de tests complet pour votre projet TypeScript.
1. Tests unitaires : vérifier les unités atomiques de code
Les tests unitaires sont le niveau de vérification le plus rapide et le moins coûteux. Ils isolent une fonction, une méthode ou une classe et la testent indépendamment des dépendances externes (bases de données, API, système de fichiers).
1.1 Choix de l'outil et configuration
Le framework le plus populaire pour TypeScript est Jest. Il prend en charge TypeScript nativement via ts-jest ou @swc/jest.
// Installation
npm install --save-dev jest @types/jest ts-jest
// Initialisation de la configuration
npx ts-jest config:init
Après cela, un fichier jest.config.js avec les paramètres apparaîtra à la racine.
1.2 Exemple de test unitaire pour une fonction
Supposons que nous ayons une fonction simple pour calculer une remise :
// discount.ts
export function applyDiscount(price: number, discountPercent: number): number {
if (discountPercent < 0 || discountPercent > 100) {
throw new Error('Pourcentage de remise invalide');
}
return price - (price * discountPercent) / 100;
}
Écrivons un test unitaire pour celle-ci :
// discount.test.ts
import { applyDiscount } from './discount';
describe('applyDiscount', () => {
it('doit calculer correctement une remise de 10%', () => {
expect(applyDiscount(1000, 10)).toBe(900);
});
it('doit retourner le prix d\'origine pour une remise de 0%', () => {
expect(applyDiscount(500, 0)).toBe(500);
});
it('doit lever une erreur pour une remise supérieure à 100%', () => {
expect(() => applyDiscount(100, 150)).toThrow('Pourcentage de remise invalide');
});
});
Exécutez : npx jest. Les trois tests devraient passer au vert.
1.3 Mocks et stubs
Si une fonction accède à un service externe (par exemple, une base de données), nous le remplaçons par un mock :
// userService.test.ts
import { getUser } from './userService';
import { db } from './database';
jest.mock('./database'); // Mock automatique
test('getUser retourne l\'utilisateur par ID', async () => {
(db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Ivan' });
const user = await getUser(1);
expect(user.name).toBe('Ivan');
});
2. Tests d'intégration : vérifier l'interaction des modules
Les tests d'intégration vérifient comment différentes parties du système fonctionnent ensemble : contrôleurs, services, repositories, bases de données. Ils sont plus lents que les tests unitaires, mais offrent une plus grande confiance dans la correction des assemblages.
2.1 Tester un point d'API avec Express
Pour tester les requêtes HTTP, nous utilisons la bibliothèque Supertest avec Jest.
// Installation
npm install --save-dev supertest @types/supertest
Supposons que nous ayons une application Express :
// app.ts
import express from 'express';
import { createUser } from './controllers/userController';
export const app = express();
app.use(express.json());
app.post('/api/users', createUser);
Test d'intégration :
// app.test.ts
import request from 'supertest';
import { app } from './app';
```