Jest
Pourquoi tester ?
Les tests permettent simplement de s'assurer du bon fonctionnement et de la qualité de votre code. En effet pour des projets aux fonctionnalités très simple il peut sembler évident, juste en démarrant notre application et en l'utilisant manuellement, qu'elle fonctionne. Mais au fur et à mesure de son évolution vous allez ajouter des aspects de plus en plus complexe.
Les test peuvent aussi servir à prévenir les soucis de régression. Imaginons que votre code évolue et une fonctionnalité est atteinte par une modification de votre code sans que vous ne vous en rendiez compte. Les test automatisés eux passeront sur l'entièreté de votre code et réussiront à détecter cette erreur.
Types de tests
Dans le vocabulaire professionnel vous entendez de nombreux types de tests, dont voici une liste non exhaustive:
- Unit Tests (Tests unitaires)
- Testent des fonctions ou des composants individuels en isolation.
- Exemple : Vérifier que la fonction
add(2, 3)
retourne5
.
- Integration Tests (Tests d'intégration)
- Testent la combinaison de plusieurs unités ou modules pour s'assurer qu'ils fonctionnent ensemble.
- Exemple : Vérifier que l'API d'une base de données renvoie les données correctes.
- End-to-End Tests (Tests E2E)
- Simulent le comportement des utilisateurs dans un environnement réel pour tester un système complet.
- Exemple : Vérifier qu'un utilisateur peut s'inscrire sur un site web.
- Performance Tests (Tests de performance)
- Mesurent la rapidité et la réactivité du système.
- Exemple : Vérifier que le chargement d'une page ne dépasse pas 3 secondes.
- Accessibility Tests (Tests d'accessibilité)
- S'assurent que le système est utilisable par tous, y compris les personnes en situation de handicap.
- Exemple : Vérifier que tous les boutons ont des étiquettes ARIA appropriées.
- UI Tests (Tests d'interface utilisateur)
- Vérifient que l'interface utilisateur s'affiche et fonctionne comme prévu.
- Exemple : S'assurer qu'une icône apparaît au bon endroit dans une application.
Le framework Jest
Pour réaliser des tests en NodeJS on peut utiliser le package jest
et ts-jest
pour les tests en typescript. Le package @types/jest
est aussi nécessaire pour les tests en typescript.
Commençons par installer nos packages:
npm install -D jest ts-jest @types/jest`
Configuration
Pour commencer nous allons créer un fichier de configuration pour Jest. Créer un fichier jest.config.ts
qui contiendra la configuration de Jest pour fonctionner avec TypeScript.
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'node',
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
};
export default config;
Modifier ensuite votre package.json en lui ajoutant un script
supplémentaire: "test": "jest"
.
Vous pouvez désormais lancer la commande npm run test
qui cherchera tout vos fichier de types *.spec.js
ou *.test.js
.
En effet ces fichiers définissent des fichiers de test, il est généralement recommandé de créer un fichier *.test.js
pour chaque fichier que vous posséder. Par exemple si nous avons un fichier math.js
qui assumera des fonctions mathématique, nous créerons son fichier de test correspondant math.test.js
.
Premier test unitaire
Commençons avec des tests unitaires très simple. Nous allons essayer de tester une fonction sum
contenue dans le fichier math.ts
comme celle que nous avions définie plus tôt dans ce cours.
Créer un fichier tests/math.test.js
et ajoutez y les lignes suivantes:
import { sum } from '../src/math';
describe('sum function', () => {
test('adds 1 + 2 to equal 3', () => {
const res = sum(1, 2);
expect(res).toBe(3);
});
});
test
indique que nous allons créer un test avec son intitulé puis il prend en second paramètre un callback de ce que le test doit vérifier.expect
correspond à l'attendu.toBe
est une fonction qui vérifie que le résultat de la fonctionsum
est bien égal à 3.
Lancez la commande npm run test
et nous constaterons que les tests sont au vert indiquant que la fonction a bien été testé avec les paramètres donnés.
Vous trouverez la liste des fonctions de comparaison ici.
Hiérarchisation des tests
Supposons que nous voulons tester plusieurs fonctions différentes et que chaque fonction possèdera sa propre batterie de test. Il est possible de hiérarchier les tests grâce à la fonction describe
.
describe('sum function', () => {
test('adds 1 + 2 to equal 3', () => {
const res = sum(1, 2);
expect(res).toBe(3);
});
test('add -1 + 0', () => {
const res = sum(-1, 0);
expect(res).toBe(-1);
});
});
Remarque : Depuis le début nous utilisons le mot clé
test
pour définir un test. Il est possible de le remplacer parit
qui est un alias. C'est d'ailleurs souvent préféré pour des raisons de lisibilité. À partir de maintenant nous utiliseronsit
pour définir nos tests dans ce cours.
Coverage
Le coverage est une notion importante dans les tests. Il permet de savoir si l'ensemble de votre code est testé. Jest permet de générer un rapport de couverture de code pour savoir si l'ensemble de votre code est testé ou non sous forme de pourcentage ou alors d'un rapport détaillé ligne par ligne.
Pour ajouter le coverage dans vos test il suffit d'ajouter le suffixe --coverage à votre commande jest --coverage
.
Vous trouverez le résultat du coverage dans le dossier coverage/index.html
.
Seuil de couverture
Vous pouvez définir un seui de couverture pour vos tests. S'il n'est pas atteint, les tests seront considérés comme échoués. On le définit dans le fichier jest.config.js
avec la clé coverageThreshold
. Par exemple, pour définir un seuil de 90% pour chaque type de couverture (branches, functions, lines, statements), ajoutez le code suivant :
module.exports = {};
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'node',
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
coverageThreshold: {
global: {
branches: 90,
functions: 90,
lines: 90,
statements: 90,
},
},
};
export default config;
Remarque: On appréciera généralement un seuil de couverture de 90% ou plus pour un projet en production.
Environnement de test
Notre but est maintenant de tester une API. Cependant un problème va se poser. En effet, si nous testons une API, nous allons devoir y faire appel et fatalement altérer les données de la base en les supprimant ou en les modifiant. Pour éviter cela il y a plusieurs méthodes:
- Utiliser une base de données de test.
- Utiliser un mock de la base de données, c'est-à-dire changer les retours des fonctions qui font appel à la base de données.
- Utiliser une base de données en mémoire, qui sera supprimée à la fin des tests.
Chacune de ces solutions est viable et peut dépendre d'un contexte. Comme nous voulons une solution simple et rapide nous allons préférer mocker la base de données.
Mocking de prisma
Pour mocker la base de données nous allons utiliser le package jest-mock-extended
. Ce package permet de mocker des fonctions et de les remplacer par des fonctions vides ou des fonctions qui renvoient des valeurs spécifiques.
Il faut préciser avant le démarrage des tests qu'il faut remplacer notre singleton 'client.ts' par le mock. Cela peut se faire à travers un fichier de setup tests/jest.setup.ts
qui sera appelé avant le lancement des tests.
import { PrismaClient } from '@prisma/client';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import prisma from '../src/client';
jest.mock('../src/client', () => ({
__esModule: true,
default: mockDeep<PrismaClient>(),
}));
beforeEach(() => {
mockReset(prismaMock);
});
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>;
Il faut aussi modifier le fichier jest.config.ts
pour lui indiquer le fichier de setup.
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'node',
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
coverageThreshold: {
global: {
branches: 90,
functions: 90,
lines: 90,
statements: 90,
},
},
setupFilesAfterEnv: ['./tests/jest.setup.ts'],
};
export default config;
Création de tests
Maintenant nous allons créer un test pour une route de notre API. Commençons par tester la route /users
qui retourne tous les utilisateurs de notre base de données. Nous utilisons le package supertest
pour tester notre API. Il permet de faire des requêtes HTTP sur notre serveur depuis les tests.
On ajoutera aussi le mock de prisma dans notre fichier de test. Et on modifie le retour de la fonction findMany
grâce à la fonction mockResolvedValue
.
import request from 'supertest';
import { app, stopServer } from '../src';
import { prismaMock } from './jest.setup';
import jwt from 'jsonwebtoken';
afterAll(() => {
stopServer();
});
describe('GET /users', () => {
it('should return an array of users', async () => {
prismaMock.user.findMany.mockResolvedValue([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
const response = await request(app).get('/users');
expect(response.status).toBe(200);
expect(response.body).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
});
});