Add Cucumber for readable BDD-style integration tests (apps/api)
Coexists with Mocha (kept for unit-style tests) and Cypress (unchanged, web e2e). Adds: - apps/api/features/*.feature — Gherkin scenarios - apps/api/features/step-definitions/*.steps.ts — step implementations - apps/api/features/support/world.ts — per-scenario World, spins up the Express app in-process via createApp() + supertest (no real server needed, same approach as the existing Mocha health test) - apps/api/cucumber.cjs — config, deliberately .cjs (not .js) to avoid the same ESM/CJS config-loading mismatch that broke apps/web/cypress.config.ts earlier - `test:bdd` script (cross-env + tsx via NODE_OPTIONS=--import=tsx, for cross-platform ESM+TS loading) - health.feature/steps as a working example, mirroring the existing Mocha health test so both suites cover the same behavior in their respective styles CI: runs `pnpm --filter api test:bdd` alongside the existing test step. README: documents the new test layer and the TS/ESM config-loading caveat for future tool configs. Verified locally: lint, mocha, cucumber, and full build all pass.
This commit is contained in:
parent
746100e257
commit
47e0477c40
8 changed files with 687 additions and 13 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -22,6 +22,7 @@ jobs:
|
|||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm lint
|
||||
- run: pnpm --filter api test
|
||||
- run: pnpm --filter api test:bdd
|
||||
- run: pnpm build
|
||||
|
||||
e2e:
|
||||
|
|
|
|||
38
README.md
38
README.md
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Monorepo pnpm workspaces :
|
||||
|
||||
- `apps/api` — backend Express/TypeScript (squelette générique : healthcheck, config env, Prisma non modélisé)
|
||||
- `apps/api` — backend Express/TypeScript (squelette générique : healthcheck, config env, Prisma non modélisé, tests Mocha + Cucumber/BDD)
|
||||
- `apps/web` — frontend React/Vite/TypeScript (squelette générique, prêt à être embarqué par Capacitor plus tard)
|
||||
- `packages/shared` — code partagé entre `api` et `web` (types, schémas de validation, constantes) — vide pour l'instant
|
||||
|
||||
|
|
@ -68,11 +68,37 @@ pnpm dev:web
|
|||
## Qualité / Tests
|
||||
|
||||
```bash
|
||||
pnpm lint # Biome (lint + format check)
|
||||
pnpm lint:fix # Biome --write
|
||||
pnpm test # tests unitaires/intégration (Mocha, apps/api)
|
||||
pnpm --filter web e2e # tests e2e (Cypress, démarre le serveur dev automatiquement)
|
||||
pnpm build # build de tous les workspaces
|
||||
pnpm lint # Biome (lint + format check)
|
||||
pnpm lint:fix # Biome --write
|
||||
pnpm test # tests unitaires/intégration (Mocha, apps/api)
|
||||
pnpm --filter api test:bdd # tests d'intégration BDD (Cucumber/Gherkin, apps/api)
|
||||
pnpm --filter web e2e # tests e2e (Cypress, démarre le serveur dev automatiquement)
|
||||
pnpm build # build de tous les workspaces
|
||||
```
|
||||
|
||||
La CI GitHub Actions (`.github/workflows/ci.yml`) exécute lint + tests + build sur chaque push/PR vers `main`, puis les tests e2e Cypress.
|
||||
|
||||
### Cucumber (apps/api)
|
||||
|
||||
Tests d'intégration lisibles en Gherkin, en complément de Mocha (qui reste pour les
|
||||
tests unitaires purs) :
|
||||
|
||||
- `apps/api/features/*.feature` — scénarios en Given/When/Then (`health.feature` sert
|
||||
d'exemple)
|
||||
- `apps/api/features/step-definitions/*.steps.ts` — implémentation des steps
|
||||
- `apps/api/features/support/world.ts` — contexte partagé entre les steps d'un
|
||||
scénario (instancie l'app Express in-process via `createApp()`, comme le fait déjà
|
||||
supertest côté Mocha — pas besoin de lancer un vrai serveur)
|
||||
- `apps/api/cucumber.cjs` — config (extension `.cjs` volontaire, voir la remarque
|
||||
TypeScript/ESM ci-dessous)
|
||||
|
||||
Pour ajouter un scénario : écrire le `.feature`, lancer `pnpm --filter api test:bdd`,
|
||||
implémenter les steps manquants (Cucumber affiche des snippets tout prêts pour ceux
|
||||
qui n'existent pas encore).
|
||||
|
||||
> **Piège TypeScript/ESM à connaître** (déjà rencontré avec `cypress.config.ts`) :
|
||||
> les fichiers de config d'outils tiers qui font du chargement dynamique de TS
|
||||
> (`cucumber.cjs`, `cypress.config.ts`…) sont sensibles au `"type": "module"` du
|
||||
> `package.json`. `cucumber.cjs` évite le problème *pour sa propre config* en étant
|
||||
> explicitement CommonJS ; les steps/world restent en `.ts` ESM classique et sont
|
||||
> chargés via `tsx` (`NODE_OPTIONS=--import=tsx`, voir le script `test:bdd`).
|
||||
|
|
|
|||
11
apps/api/cucumber.cjs
Normal file
11
apps/api/cucumber.cjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Explicit .cjs extension (not .js) so this loads as CommonJS regardless of
|
||||
// the "type": "module" in package.json — avoids the same ESM/CJS config
|
||||
// loader mismatch that broke apps/web/cypress.config.ts earlier.
|
||||
module.exports = {
|
||||
default: {
|
||||
paths: ["features/**/*.feature"],
|
||||
import: ["features/**/*.ts"],
|
||||
format: ["progress-bar"],
|
||||
formatOptions: { snippetInterface: "async-await" },
|
||||
},
|
||||
};
|
||||
12
apps/api/features/health.feature
Normal file
12
apps/api/features/health.feature
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Feature: API health check
|
||||
As a monitoring service
|
||||
I want to query the API
|
||||
So that I can verify it is up and responding correctly
|
||||
|
||||
Scenario: The API is available
|
||||
When I send a GET request to "/health"
|
||||
Then the response status should be 200
|
||||
And the response body should be:
|
||||
"""
|
||||
{ "status": "ok" }
|
||||
"""
|
||||
16
apps/api/features/step-definitions/health.steps.ts
Normal file
16
apps/api/features/step-definitions/health.steps.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { Then, When } from "@cucumber/cucumber";
|
||||
import request from "supertest";
|
||||
import type { CustomWorld } from "../support/world.js";
|
||||
|
||||
When("I send a GET request to {string}", async function (this: CustomWorld, path: string) {
|
||||
this.response = await request(this.app).get(path);
|
||||
});
|
||||
|
||||
Then("the response status should be {int}", function (this: CustomWorld, status: number) {
|
||||
assert.equal(this.response.status, status);
|
||||
});
|
||||
|
||||
Then("the response body should be:", function (this: CustomWorld, expectedJson: string) {
|
||||
assert.deepEqual(this.response.body, JSON.parse(expectedJson));
|
||||
});
|
||||
18
apps/api/features/support/world.ts
Normal file
18
apps/api/features/support/world.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { type IWorldOptions, World, setWorldConstructor } from "@cucumber/cucumber";
|
||||
import type { Express } from "express";
|
||||
import type request from "supertest";
|
||||
import { createApp } from "../../src/app.js";
|
||||
|
||||
// Fresh Express app per scenario (in-process, via supertest — no server to
|
||||
// spin up/tear down) plus the last HTTP response, available to every step.
|
||||
export class CustomWorld extends World {
|
||||
app: Express;
|
||||
response!: request.Response;
|
||||
|
||||
constructor(options: IWorldOptions) {
|
||||
super(options);
|
||||
this.app = createApp();
|
||||
}
|
||||
}
|
||||
|
||||
setWorldConstructor(CustomWorld);
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
"build": "tsc -p tsconfig.json",
|
||||
"start": "node dist/server.js",
|
||||
"test": "mocha",
|
||||
"test:bdd": "cross-env NODE_OPTIONS=--import=tsx cucumber-js",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev"
|
||||
},
|
||||
|
|
@ -18,10 +19,12 @@
|
|||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cucumber/cucumber": "^13.2.1",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"chai": "^5.1.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"mocha": "^10.8.2",
|
||||
"prisma": "^5.22.0",
|
||||
"supertest": "^7.0.0",
|
||||
|
|
|
|||
601
pnpm-lock.yaml
601
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue