Aller au contenu

Test Conventions

Tronc commun unit + intégration — philosophie, gotchas Vitest Bricks, AI-slop checklist. Contrat intégration API : integration-test-conventions.mdc. Skills : upsert-unit-tests, upsert-integration-tests (création guidée), review-integration-tests (review du diff).

Stack

  • API + common packages (api, helpers, api-communication) : Vitest
  • Front apps (front-app, front-mobile-app) : Jest (migration future)
  • DO NOT mix Jest and Vitest APIs in the same project

Scope : tester le custom, pas la lib

  • DO tester la logique custom qu'on ajoute (refinements .and(...), business functions, helpers maison, orchestration service)
  • DO NOT retester la librairie sous-jacente : pas de happy-path qui passerait avec ou sans la logique custom. Si un test passe que le refinement soit présent ou non, il teste la lib (ex. idonttrustlikethat, zod, dayjs), pas ton code → le supprimer
  • Règle de pouce : chaque test doit tomber si on retire la logique custom qu'il est censé couvrir
  • Unit test = logique pure uniquement. Mocker des modules internes (DB, services, providers) pour tester unitairement = mauvaise cible → test d'intégration (API) ou pas de test
  • Frugalité : peu de tests mais représentatifs de la réalité sur laquelle on compte — pas de N variations du même chemin qui n'ajoutent aucune information ; N variations du même calcul → table-driven (it.each)

Vitest — spécificités Bricks

  • Globals (describe, it, expect, vi, hooks) — pas d'import
  • Fake timers activés globalement via src/test-setup.ts ; instant précis → vi.setSystemTime(new Date(2024, 0, 15)), reset en afterEach si le test change l'heure
  • DO NOT jest.fn() / jest.mock() / jest.spyOn() — n'existent pas en Vitest → vi.* ; DO NOT assertions chai (expect(...).to.equal())
  • File naming : API *.unit-test.ts, common packages *.test.ts / *.spec.ts — dans des dossiers __test__/ ou __tests__/
  • Config : root vitest.config.base.ts partagé, chaque projet mergeConfig(base, defineProject({ ... })) ; types vitest dans tsconfig.spec.json (jamais dans tsconfig.build.json)

Littéraux de date

new Date('2024-01-01') — l'ISO date-only vaut minuit UTC (spec ECMAScript), déterministe sur toute machine/CI. Pas de suffixe T00:00:00.000Z (bruit), pas de dayjs('2024-01-01') (parse en heure locale → flake CI vs local ; dayjs.utc(...).toDate() est plus long pour zéro gain). Besoin d'un instant intra-journée → forme complète avec zone ('2024-01-01T09:30:00Z').

Integration tests (API)

API integration tests live next to the code they test (src/<module>/__tests__/<feature>.integration-test.ts) and run against a local Postgres + Redis stack (docker compose).

Contract: API integration test conventions. Guided creation + full how-to (fixtures, auth, MSW, cron/worker, dev loop): upsert-integration-tests skill. Diff review: review-integration-tests skill.

AI-slop checklist

Patterns que l'IA produit par défaut sur les tests — rejeter en review :

  • expect(res.body.x).toBe(...) × N → un seul toMatchObject({ ... })
  • ❌ Snapshot d'une réponse HTTP → inline le JSON attendu (sinon --update-snapshots cimente le bug)
  • ❌ Asserter la copie FR ('Compte introuvable.') → assert sur statusCode + code business, pas la trad
  • try { ... } catch (e) { expect(e).toBeDefined() }await expect(...).rejects.toThrow(SomeError)
  • ❌ Mock du service que le controller appelle dans un test d'intégration → c'est un test unitaire déguisé
  • it('works'), it('returns 200'), it('handles errors') → nommer l'observable précis
  • db.{selectFrom,insertInto,updateTable,deleteFrom}(...) dans *.integration-test.ts → reads via <Module>Repository, writes via seed<Entity> fixture
  • ❌ URL string hardcodée pour une route Bricks ('/investor/portfolio/...') → import du descriptor api-communication[-bricksoffice] (exception : better-auth)
  • ❌ Happy-path only → chaque route a ≥ 1 Quand <erreur>
  • ❌ Asserter via le même GET que le SUT exerce → utiliser TestApi pour les side-effects (sinon test tautologique)
  • ❌ Générer un test à partir du code sans connaître l'attendu → cimente le bug comme spec (cf. KeelCode 2025)