| name | ts-testing |
| description | Generates dense law-driven test suites for Effect modules via algebraic PBT using Vitest, @effect/vitest, fast-check. Enforces 175 LOC cap, 95% per-file coverage, mutation kill-ratio via Stryker. Use when writing/reviewing spec files, adding coverage, generating property tests, debugging flakes, or scaffolding unit/integration/E2E tests from templates. |
[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:
[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 to Arbitrary.make(Schema) for custom filtering. Hand-roll with fc.* 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 single it.effect with Effect.all aggregation. 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, or contract template from templates/.
- 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. Run pnpm exec nx test -- --coverage to confirm.
- Run mutation testing --
npx stryker run to verify tests kill mutants. Stryker TS checker eliminates compile-error mutants; vitest.related: true scopes 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 test
effect -- Effect, FastCheck as fc, Schema as S, Array as A
vitest -- 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.mock must execute before tests.
[IMPORTANT]:
- [ALWAYS] Extract repeated orchestration — when 3+ tests repeat the same
Effect.gen + pipe + _provide pattern, 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 _provide call 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.
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 to it.effect.prop.
- [ALWAYS] Use
it.scoped for tests acquiring Scope-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.prop invocation 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]:
Stryker Integration:
- [ALWAYS] Run
npx stryker run after semantic gate passes.
- [ALWAYS] Verify TS checker is enabled in
stryker.config.mjs (checkers: ['typescript']).
- [ALWAYS] Use
vitest.related: true for per-mutant test scoping.
- [ALWAYS] Use dynamic concurrency (remove hardcoded
concurrency setting).
[REFERENCE] Detailed checklists, error symptoms, and commands: ->validation.md. Hook rules and anti-patterns: ->guardrails.md.