[H1][TS-TESTING]
Dictum: Algebraic property-based testing eliminates circular AI-generated tests by construction.
Generate dense, law-driven test suites. Pure transformations tested via algebraic laws; effectful boundaries tested via properties. Density over quantity -- few powerful tests, not many trivial ones.
Tasks:
- Classify test category via section 4.
- For unit PBT: read ->laws.md, ->density.md.
- For integration/E2E: read ->categories.md.
- Author spec following workflow in section 1.
- Validate against section 8 and ->validation.md.
Versions: Vitest (inline projects), fast-check (PBT + model-based + fc.gen), @effect/vitest (Effect bridge + Schema-as-arbitraries), Stryker (mutation + TS checker), testcontainers (integration), Playwright (E2E).
References:
| [INDEX] | [DOMAIN] | [FILE] |
|---|---|---|
| [1] | Laws | laws.md |
| [2] | Density | density.md |
| [3] | Categories | categories.md |
| [4] | Guardrails | guardrails.md |
| [5] | Validation | validation.md |
| [6] | Template | unit-pbt.spec.template.md |
| [7] | Template | integration.spec.template.md |
| [8] | Template | contract.spec.template.md |
[1][WORKFLOW]
Dictum: Systematic workflow ensures complete, dense, non-circular tests.
- Read source module -- Inventory exported functions, Effect dependencies (R channel), error types (E channel), schemas, branded types.
- Classify test category -- Walk routing table from section 4. Most
packages/server/modules are unit PBT. Boundary modules (database, HTTP) are integration. - Select applicable laws -- Walk matrix from section 5 per exported function. List each law with formula.
- Design arbitraries -- Pass Schema directly to
it.effect.prop(auto-extracts arbitrary). Fall back toArbitrary.make(Schema)for custom filtering. Hand-roll withfc.*only when schema unavailable. Use_prefix. - Plan LOC budget -- Allocate lines per section within 175 cap: imports 5-7, constants 8-15, layer 3-6, algebraic 80-120, edge cases 15-25.
- Pack and merge -- Combine 2-4 laws sharing same arbitrary shape into single
it.effect.prop. Merge edge cases sharing the same mock/layer config into singleit.effectwithEffect.allaggregation. Extract repeated service-call orchestration into[FUNCTIONS]helper before merging. - Select density technique -- Walk table from section 6. Choose highest-multiplier technique that fits.
- Author spec from template -- Pick
unit-pbt,integration, orcontracttemplate fromtemplates/. - Verify oracle independence -- Walk guardrails checklist from ->guardrails.md. Expected values come from laws, external refs, or standards -- never from re-deriving source logic.
- Verify coverage -- Predict branch coverage from properties. Add
[EDGE_CASES]section if <95% predicted. Runpnpm exec nx test -- --coverageto confirm. - Run mutation testing --
npx stryker runto verify tests kill mutants. Stryker TS checker eliminates compile-error mutants;vitest.related: truescopes test runs.
[2][HARD_CONSTRAINTS]
Dictum: Numeric thresholds are non-negotiable.
[CRITICAL]:
- [ALWAYS] 175 LOC flat cap per spec file. No exceptions.
- [ALWAYS] 95% per-file coverage (V8 provider, statements + branches + functions). Each file measured independently.
- [ALWAYS] Mutation thresholds: break=50 (build fails), low=60 (investigate), high=80 (target).
- [ALWAYS] One spec per source module per category. No splitting, no combining.
- [ALWAYS] Density over quantity -- pack laws into properties, aggregate with Effect.all, leverage generation.
| [INDEX] | [METRIC] | [VALUE] | [NOTE] |
|---|---|---|---|
| [1] | Max file LOC | 175 | Flat cap, enforced by PostToolUse |
| [2] | Coverage target | 95% | Per-file V8 (statements+branches+fn) |
| [3] | Mutation break | 50 | Build fails below this |
| [4] | Mutation low | 60 | Investigation trigger |
| [5] | Mutation high | 80 | Target -- contract-driven tests |
[3][FILE_STRUCTURE]
Dictum: Canonical section order enables rapid navigation.
Import Order (enforced by hook):
@effect/vitest--it,layer@parametric-portal/*-- source module under testeffect--Effect,FastCheck as fc,Schema as S,Array as Avitest--expect- External oracles (e.g.,
node:crypto) -- after vitest
Section Order (omit unused, separators padded to column 80):
// --- [CONSTANTS] -------------------------------------------------------------
// --- [FUNCTIONS] -------------------------------------------------------------
// --- [LAYER] -----------------------------------------------------------------
// --- [MOCKS] -----------------------------------------------------------------
// --- [ALGEBRAIC] -------------------------------------------------------------
// --- [EDGE_CASES] ------------------------------------------------------------
Section Descriptions:
| [INDEX] | [SECTION] | [PURPOSE] |
|---|---|---|
| [1] | [CONSTANTS] |
Arbitraries (_ prefix), static data (UPPER), test fixtures |
| [2] | [FUNCTIONS] |
Test helpers: _provide, _reset, _mkDatabase, _live — reusable |
| factory/provider/reset functions that wire services or build fakes | ||
| [3] | [LAYER] |
layer() wrapper for suites sharing a service layer |
| [4] | [MOCKS] |
vi.mock(...) calls for module-level mocks (database client, telemetry) |
| [5] | [ALGEBRAIC] |
PBT properties (it.effect.prop) and law-driven behavioral tests |
| [6] | [EDGE_CASES] |
Boundary conditions, rejection paths, specific regression tests |
[CRITICAL]:
- [NEVER] Remove
[FUNCTIONS]or[MOCKS]sections when they exist in a spec file. - [ALWAYS] Place
[FUNCTIONS]between[CONSTANTS]and[LAYER]— it holds test-scoped helpers. - [ALWAYS] Place
[MOCKS]after[LAYER]and before[ALGEBRAIC]—vi.mockmust execute before tests.
[IMPORTANT]:
- [ALWAYS] Extract repeated orchestration — when 3+ tests repeat the same
Effect.gen+pipe+_providepattern, extract to a named helper (e.g._transition) in[FUNCTIONS]. - [ALWAYS] Inline trivial mock factories — if a factory returns a single-field literal (
{ log: vi.fn(() => Effect.void) } as never), inline at_providecall site; reserve_mk*factories for mocks with 2+ configurable fields. - [ALWAYS] Terse internal names — test helpers use short parameter names (
a,ns,set) to maximize LOC budget; they are not public API.
Constants Convention:
| [INDEX] | [PREFIX] | [PURPOSE] | [EXAMPLE] |
|---|---|---|---|
| [1] | _ |
Fast-check arbitraries | _json, _text, _nonempty |
| [2] | _ |
Schema-derived arbs | _item = Arbitrary.make(ItemSchema) |
| [3] | UPPER |
Static constants | CIPHER, RFC6902_OPS, NIST_VECTORS |
| [4] | as const |
All static constants | { iv: 12, tag: 16 } as const |
| [5] | _ |
Single-expression arbs | _dns = fc.tuple(…).map(…) (1 line) |
File Comment: /** [Module] tests: [brief description]. */
Topology: See section 4 for file locations per category.
[4][CATEGORY_ROUTING]
Dictum: Category selection determines environment, tools, and patterns.
Categories: Unit PBT (tests/packages/), Integration (tests/integration/), Contract (tests/packages/{pkg}/ or tests/integration/), System (tests/system/), E2E (tests/e2e/).
Quick route: Most packages/server/ modules are Unit PBT. Boundary modules (database, HTTP, Redis) are Integration. Cross-package schema/service compatibility is Contract. Cross-service orchestration is System. User-facing flows are E2E (Playwright agent pipeline -- do not manually author).
| [INDEX] | Category | [LOCATION] | [VERIFIES] | [KEY_TOOLS] |
|---|---|---|---|---|
| [1] | Unit PBT | tests/packages/{pkg}/ |
Algebraic properties of pure domain logic | @effect/vitest, fast-check |
| [2] | Integration | tests/integration/ |
Boundary contracts against real infrastructure | testcontainers, MSW |
| [3] | Contract | tests/packages/{pkg}/ or tests/integration/ |
Schema compatibility, service tag availability, layer composition | @effect/vitest, Effect.provideService, Schema.decodeUnknown |
| [4] | System | tests/system/ |
Cross-service orchestration via Layer composition | Full Layer stack, mock edges |
| [5] | E2E | tests/e2e/ |
User-facing flows through full application stack | Playwright, agent pipeline |
Contract tests use no mocks -- real schemas and service tags with Layer.succeed for minimal fakes. They verify that packages remain structurally compatible at their boundaries.
[REFERENCE] Full routing matrix, Vitest inline projects, and patterns per category: ->categories.md.
[5][LAW_SELECTION]
Dictum: Laws define WHAT to test; density techniques define HOW.
Walk the law taxonomy per exported function. Select all applicable laws. Pack laws sharing the same arbitrary shape into a single it.effect.prop.
Core laws: Identity, Inverse, Idempotent, Commutative, Associative, Homomorphism, Annihilation, Monotonicity.
Equivalence relations: Reflexive, Symmetric, Transitive.
Structural properties: Immutability, Determinism, Non-determinism, Length formula, Preservation.
Concurrency laws: Fiber interruption safety, Resource cleanup (acquireRelease bracket), Ref/TRef atomicity.
Type-level laws: Compile-time assertions via expectTypeOf and @ts-expect-error.
Domain invariants: Security (proto pollution, tenant isolation, tampering), Boundary limits, Known-answer vectors.
[REFERENCE] Full law taxonomy, selection procedure, and code patterns: ->laws.md.
[6][DENSITY_SELECTION]
Dictum: Parametric generation multiplies coverage per LOC.
Top techniques: it.effect.prop PBT (50-200x), property packing (2-4x), Effect.all aggregation (Nx1), statistical batching, symmetric iteration, model-based commands, fc.scheduler() race detection.
Decision heuristic: Start at highest multiplier. Drop to lower-multiplier techniques only when property shape or cost prevents a higher one.
[CRITICAL] Block syntax rule: it.effect.prop callbacks must return void | Effect<void>. Use { expect(...); } not bare expect(...). Expression-form returns an Assertion object, causing false failures.
[REFERENCE] Full technique catalog, decision rules, and code patterns: ->density.md.
[6.1][EFFECT_TESTING_API]
Dictum: @effect/vitest exposes a rich API -- use all of it.
[IMPORTANT] Schema-as-arbitraries: Pass Schema directly to it.effect.prop -- no Arbitrary.make() wrapping needed. The framework auto-creates arbitraries.
// Schema passed directly -- no Arbitrary.make() wrapper
it.effect.prop('roundtrip', { req: NotificationService.Request }, ({ req }) =>
S.encode(NotificationService.Request)(req).pipe(
Effect.flatMap(S.decodeUnknown(NotificationService.Request)),
Effect.tap((decoded) => { expect(decoded).toEqual(req); })));
| [INDEX] | [API] | [WHEN_TO_USE] | [BENEFIT] |
|---|---|---|---|
| [1] | it.effect |
Standard Effect tests with TestServices | Auto-provides TestClock, etc. |
| [2] | it.effect.prop |
PBT with Schemas or fc.Arbitrary | 50-200x coverage per LOC |
| [3] | it.scoped |
Tests needing Scope (semaphores, fibers) | Auto-release on test end |
| [4] | it.live |
Tests needing real Clock/Random | No TestServices overhead |
| [5] | it.effect.each |
Parameterized Effect tests | Table-driven with Effect runtime |
| [6] | it.flakyTest |
Retry flaky Effect until timeout | Removes E from type |
| [7] | layer() + it.layer() |
Share/nest service layers across tests | Single materialization per suite |
| [8] | Schema as arbitrary | Pass Schema directly to .prop arbitraries |
Zero boilerplate, stays synced |
| [9] | fc.scheduler() |
Race condition detection in concurrent ops | Adversarial interleaving |
| [10] | expectTypeOf |
Compile-time type assertions | Type-level correctness |
[CRITICAL]:
- [NEVER] Use
Arbitrary.make(Schema)when Schema can be passed directly toit.effect.prop. - [ALWAYS] Use
it.scopedfor tests acquiringScope-managed resources. - [ALWAYS] Use
layer()wrapper for suites sharing a service layer.
[7][THESIS]
Dictum: Three pillars eliminate circular AI-generated tests.
- Algebraic PBT as External Oracle -- Laws (identity, inverse, homomorphism) are domain-independent mathematical truths. AI cannot fabricate a law that "happens to pass" -- law correctness is provable independent of implementation.
- Parametric Generation Multiplies Coverage -- Single
it.effect.propinvocation generates 50-200 cases per run, replacing hundreds of hand-written assertions with single universally-quantified property. - Mutation Testing Detects Circular Tests -- Stryker injects code mutants (operator swaps, conditional negations, statement deletions). Circular tests re-deriving expected values from source code fail to kill mutants.
Seven-Layer Defense:
| [INDEX] | [LAYER] | [MECHANISM] | [STATUS] |
|---|---|---|---|
| [1] | Algebraic PBT | Laws are external oracles by nature | Active |
| [2] | Model-Based Testing | fc.commands() stateful verification | Active |
| [3] | Differential Testing | Cross-validation vs reference impls | Active |
| [4] | Mutation Testing | Stryker kill-ratio enforcement | Active |
| [5] | External Oracles | NIST FIPS 180-4, RFC 4231, RFC 6902 | Active |
| [6] | PostToolUse Hook | 13-rule awk validator | Active |
| [7] | Human Review | Final gate for spec correctness | Always |
[8][VALIDATION]
Dictum: Gates prevent non-compliant output.
[VERIFY]:
- Thresholds met (section 2 above)
- File structure matches (section 3 above)
- No forbidden patterns (13 hook rules)
- Properties return
void | Effect<void>(block syntax) - Oracle independence verified -- no re-derivation of source logic
- Schema passed directly to
it.effect.propwhere possible (noArbitrary.makewrapping) - Contract tests use no mocks -- real schemas +
Layer.succeedfor minimal fakes - No thin wrapper mock factories -- literal mocks inlined at
_providecall site - No duplicated orchestration -- repeated
Effect.gen+pipe+_provideextracted to[FUNCTIONS]helper - Stryker runs with TS checker enabled (
checkers: ['typescript'])
Stryker Integration:
- [ALWAYS] Run
npx stryker runafter semantic gate passes. - [ALWAYS] Verify TS checker is enabled in
stryker.config.mjs(checkers: ['typescript']). - [ALWAYS] Use
vitest.related: truefor per-mutant test scoping. - [ALWAYS] Use dynamic concurrency (remove hardcoded
concurrencysetting).
[REFERENCE] Detailed checklists, error symptoms, and commands: ->validation.md. Hook rules and anti-patterns: ->guardrails.md.