Aller au contenu

Tester son projet

Écrire les tests d'un site Fraza avec node:test et les outils du core, de la fonction pure à la route d'API, sans jamais toucher la base de développement.

Un site Fraza se teste avec node:test, le lanceur intégré à Node, et les outils de @fraza/core/tools/test. Aucune dépendance de test à installer. Cette page montre les trois niveaux de test, du plus rapide au plus complet. Le détail de chaque fonction est dans Tests.

Mettre en place

Dans le package.json du projet :

"scripts": {
  "test": "node --import @fraza/core/tools/test/register --test test/"
}

--import @fraza/core/tools/test/register permet à Node de charger next/server, next/link et les autres entrées de Next que le code du site importe sans extension.

Les tests qui touchent à la base lisent FRAZA_TEST_DATABASE_URL. Le .env.example d'un projet créé par @fraza/start la fait viser le PostgreSQL local : chaque suite y crée son propre schéma, puis le supprime. Elle se lit dans .env.local, ou dans le terminal, qui l'emporte :

npm test

Sans elle, ces suites sont sautées et npm test reste vert. FRAZA_DATABASE_URL, l'URL de la base de développement, n'est jamais lue par les outils de test : un test ne peut pas atteindre les données de développement par erreur.

1. Une fonction pure

Le plus rapide, sans base ni réseau : un calcul, une validation, un formatage.

// test/prix.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { totalAvecTaxes } from '../lib/prix.js';

test('ajoute la TPS et la TVQ', () => {
  assert.equal(totalAvecTaxes(100), 114.98);
});

2. Le code qui lit la base

describeWithProject donne à la suite un schéma initialisé comme une vraie base, avec les modules du projet. testUser crée un compte avec ses rôles.

// test/inscriptions.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { describeWithProject, testUser } from '@fraza/core/tools/test';
import { inscrire } from '../lib/inscriptions.js';

describeWithProject('inscriptions', () => {
  test('un compte s\'inscrit une seule fois', async () => {
    const membre = await testUser({ roles: ['user'] });
    await inscrire(membre.id, 'atelier-1');
    await assert.rejects(inscrire(membre.id, 'atelier-1'));
  });
});

3. Les routes et les pages

startTestServer lance le site sur un port libre, contre son propre schéma. apiCall l'appelle comme un navigateur, avec le cookie de session et l'en-tête Origin.

// test/api.test.js
import { after, before, test } from 'node:test';
import assert from 'node:assert/strict';
import { apiCall, startTestServer, testUser } from '@fraza/core/tools/test';

let server;
before(async () => { server = await startTestServer(); });
after(() => server.stop());

test('un visiteur ne peut pas lire les comptes', async () => {
  const { status } = await apiCall(null, 'GET', '/za/api/users', { baseUrl: server.url });
  assert.equal(status, 401);
});

test('un admin les lit', async () => {
  const admin = await testUser({ roles: ['admin'] });
  const { status } = await apiCall(admin, 'GET', '/za/api/users', { baseUrl: server.url });
  assert.equal(status, 200);
});

Le serveur tourne à côté d'un npm run dev ouvert, dans .next/test. Le next.config du projet doit lire FRAZA_NEXT_DIST_DIR, voir Serveur de test. Le premier appel attend la compilation de la page : une à deux minutes.

À la fin, stop() arrête le serveur et supprime le schéma. Rien à nettoyer, et les données de développement restent intactes.

Le stockage

Le code qui téléverse ou supprime des fichiers se teste contre un bucket en mémoire. startFakeS3 lance un serveur local qui répond comme S3 et y fait pointer les variables FRAZA_STORAGE_* : @fraza/core/storage fonctionne sans compte chez un prestataire.

// test/pieces-jointes.test.js
import { after, before, test } from 'node:test';
import assert from 'node:assert/strict';
import { startFakeS3 } from '@fraza/core/tools/test';
import { joindre } from '../lib/pieces-jointes.js';

let s3;
before(async () => { s3 = await startFakeS3(); });
after(() => s3.close());

test('la pièce jointe arrive dans le bucket', async () => {
  const key = await joindre(42, Buffer.from('bonjour'));
  assert.equal(s3.objects.has(key), true);
});

s3.state.down = true simule une panne, et s3.refuse liste des clés que le bucket refuse de supprimer. Voir Un bucket en mémoire.

Au navigateur

Pour un parcours que seul un navigateur voit (une fenêtre, un menu au clavier, un envoi de fichier), Playwright s'installe dans le projet. loginAs y ouvre la session d'un compte sans passer par le formulaire. Voir Au navigateur.

Garder ces tests pour les parcours critiques : ils sont lents, et une modification d'interface les casse plus souvent qu'un vrai bug.

Ce qu'on teste en priorité

  • L'argent : montants, taxes, remboursements.
  • Les accès : chaque route refuse un visiteur et un compte sans la permission.
  • Les données des clients : un compte ne lit jamais celles d'un autre.
  • Les migrations : elles passent sur une base qui a déjà des données. Voir Écrire une migration.

Sur cette page