@mostajs/mjs-unit
v0.3.0
Published
Micro-framework de test pour l'écosystème @mostajs/* : zéro dépendance, API minimale (test/expect/run), runner CLI (preuves HTML/JSON via --report), reporters réutilisables (./report), et un resolver ESM qui exécute sous Node les dist à imports sans exten
Maintainers
Readme
@mostajs/mjs-unit
Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later
Micro-framework de test zéro dépendance pour l'écosystème @mostajs/*. Trois apports :
- API minimale :
test(),describe(),expect()+ assertions (ok,equal,deepEqual,throwsAsync,doesNotThrowAsync). - Runner CLI (
mjs-unit <fichiers>) : importe les fichiers de test, exécute, affiche un rapport, code de sortie ≠ 0 si échec. - Resolver ESM : exécute sous Node les
dist/à imports sans extension (conventionmoduleResolution: bundlerdes modules @mostajs) — sans ce resolver,nodeéchoue surimport './x'.
Installation
npm i -D @mostajs/mjs-unit
# module local non publié : npm i -D "@mostajs/mjs-unit@file:../chemin/mosta-mjs-unit"How to use (pas à pas, dans n'importe quelle application)
- Installer le module en devDependency (voir ci-dessus).
- Créer un dossier
test-scripts/et y écrire des fichiers*.test.mjs(un test =test(name, fn); ne pas appelerrun(), le runner s'en charge). - Versionner un runner
test-scripts/run-tests.shqui (re)bâtit puis lance le CLI. - Brancher
package.json:"scripts": { "test": "bash test-scripts/run-tests.sh" }. - Lancer :
npm test(code de sortie ≠ 0 si un test échoue → utilisable en CI).
// test-scripts/api.test.mjs — test d'unité + test web, dans la même suite
import { test, equal, ok } from "@mostajs/mjs-unit";
import { createHttpTester, waitForEvent } from "@mostajs/mjs-unit/web";
import { app } from "../dist/app.js"; // votre app Express / handler natif
const api = createHttpTester(app);
test("GET /healthz → 200", async () => {
const res = await api.get("/healthz");
equal(res.status, 200);
ok(res.body.status === "ok");
});# test-scripts/run-tests.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
npm run build >/dev/null
node node_modules/@mostajs/mjs-unit/bin/cli.mjs test-scripts/*.test.mjsBesoin d'un profil d'environnement (DB de test, secrets…) avant d'importer le
dist/? Fixez-le avant les imports via un import dynamique :process.env.APP_ENV = "TEST"; const { app } = await import("../dist/app.js");(lesimportstatiques sont hoistés, donc exécutés avant le code du module).
Écrire un test (test-scripts/mon.test.mjs)
import { test, expect, ok } from "@mostajs/mjs-unit";
import { maFonction } from "../dist/lib/ma-fonction.js"; // le dist peut avoir des imports sans extension
test("addition", () => expect(maFonction(2, 2)).toBe(4));
test("async", async () => { ok(await maFonction.async()); });Ne pas appeler
run()dans le fichier : le runner s'en charge (les cas enregistrés viatest()sont exécutés groupés par fichier).
Lancer
mjs-unit test-scripts/*.test.mjs # via le bin
# ou, programmatique :
node --import @mostajs/mjs-unit/register test-scripts/mon.test.mjs # + appeler run() soi-mêmeRunner versionné conseillé (test-scripts/run-tests.sh) :
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
npm run build >/dev/null
node node_modules/@mostajs/mjs-unit/bin/cli.mjs test-scripts/*.test.mjsAPI
| Fonction | Rôle |
|---|---|
| test(name, fn) / test.skip(...) | Enregistre un cas (sync ou async) |
| describe(group, body) | Préfixe les cas du groupe |
| expect(v).toBe/toEqual/toBeTruthy/toBeFalsy/toContain | Assertions fluides |
| ok/equal/deepEqual(...) | Assertions directes |
| throwsAsync/doesNotThrowAsync(fn) | Vérifie qu'une promesse (ne) lève (pas) |
| run(label?) | Exécute les cas en attente → {passed, failed, skipped} (appelé par le CLI) |
Zéro dépendance runtime ; @types/node en dev uniquement.
Tests web (@mostajs/mjs-unit/web)
Moteur générique pour tester des applications HTTP et événementielles — sans
supertest, jest ni client socket dans le framework. Toujours zéro
dépendance (uniquement node:http/node:net).
| Fonction | Rôle |
|---|---|
| startServer(target) | Démarre un serveur HTTP éphémère (port 0) autour d'un http.Server, d'un RequestListener ou d'une app Express → { url, port, close() } |
| createHttpTester(target) | Client HTTP façon supertest : get/post/put/del/request → { status, headers, body }. target = URL d'un serveur démarré ou handler/serveur (éphémère par requête) |
| waitForEvent(emitter, name, { timeout }) | Attend un événement sur tout émetteur (EventEmitter, client Socket.io, ws…) — teste le temps réel |
import { test, equal, ok } from "@mostajs/mjs-unit";
import { createHttpTester, startServer, waitForEvent } from "@mostajs/mjs-unit/web";
// HTTP : éphémère par requête (app Express ou handler natif (req,res)=>…)
const api = createHttpTester(app);
test("login", async () => {
const res = await api.post("/login", { username: "admin", password: "admin" });
equal(res.status, 200);
ok(res.body.token);
});
// Temps réel : serveur durable + attente d'événement (ex. Socket.io)
test("push", async () => {
const srv = await startServer(httpServer); // serveur partagé
const client = ioClient(srv.url); // socket.io-client fourni par l'app
const received = waitForEvent(client, "new_ticket");
await createHttpTester(srv.url).post("/api/tickets", { serviceType: "general" });
const evt = await received;
ok(evt.ticket);
client.close();
await srv.close();
});Le client socket (
socket.io-client,ws…) reste fourni par l'application : mjs-unit ne l'embarque pas, pour préserver le zéro-dépendance.waitForEventfonctionne avec n'importe quel émetteur exposanton(idéalementonce/off).
