Skip to content

Testing Standard ​

This is the contract every project that adopts @jangkar/testing-engines must meet. jangkar-test doctor checks the mechanical parts. The CI quality gate enforces the rest. Nothing here is advisory.

1. Why this exists ​

All projects are built with Claude Code. An AI agent writes code fast and reports "tests pass" with the same confidence whether the tests are real or not. The standard exists to make the report worthless and the gate authoritative: a change is done when CI on the remote says it is done, not when the agent says so.

2. Layering rule ​

Every project separates code into three layers. Only the first two are subject to phase 1 gates.

LayerPathContainsMay importTested by
Coresrc/core/Pure business logic. Calculations, validation, state transitions, domain types.Only other core modules and the standard library. No I/O, no framework, no SDK.Unit tests + mutation testing
Adapterssrc/adapters/Everything that touches the outside world: database, HTTP clients, filesystem, third-party SDKs (Supabase, Anthropic, payment).core and external packages.Integration tests against a real local dependency
Appsrc/app/, app/, components/Routes, pages, UI, CLI entry points. Glue only.core and adapters.Phase 2 (E2E)

If logic is hard to test, it is in the wrong layer. Move it to core.

3. Test types and where they live ​

TypePathScopeReal dependencies?
Unittests/unit/**/*.test.tsOne core module in isolationNone. core has none.
Integrationtests/integration/**/*.test.tsOne adapters module against its real dependencyYes: local Supabase, sqlite, msw for third-party HTTP
Systemtests/system/**/*.system.test.tsOne use case end to end through core + adaptersYes, all local
E2Ee2e/Browser or device against a running appPhase 2

4. Gates (strict by default) ​

GateThresholdEnforced by
Lintzero errors, zero warningseslint with configs/eslint/base + tests
Layeringcore imports no I/O, framework, SDK, adapters, or app; adapters import no appconfigs/eslint/layering (in base)
Typeszero errors under configs/tsconfig/stricttsc --noEmit
Unit + integrationall green, no .only, no .skipvitest run, allowOnly: false in CI, lint rules
Coverage on core + adapters80% lines, branches, functions, statementsvitest --coverage thresholds
Mutation score on core70% break, 75% low, 90% highStryker on PRs touching src/core
System testsall greenvitest run tests/system
doctorzero violations, Claude tooling matches the pinned enginejangkar-test doctor as first CI step

5. Forbidden ​

These are lint errors or doctor failures. They do not need a reviewer to catch them.

  • it.only, describe.only, test.only
  • it.skip, describe.skip, it.todo left in a merged branch
  • expect(true).toBe(true) and other assertion-free or tautological tests
  • any, // @ts-ignore, @ts-expect-error without a description
  • Snapshot tests as the only assertion for business logic
  • Mocking the module under test, or mocking a core module from another core test
  • console.error during a passing test in CI
  • Tests that import from __mocks__ of the implementation they test
  • Commented-out tests

6. Allowed mocking ​

Mock only at the network boundary of an adapter, and only when a real local dependency is not possible:

  • Third-party paid APIs (Anthropic, payment gateways): msw request handlers with recorded fixtures.
  • Time: vi.useFakeTimers().
  • Randomness: inject a seeded generator into core.

Supabase, Postgres, Redis, and the filesystem are never mocked. Use the local instance.

7. Spec-first workflow ​

For every feature or bug fix:

  1. Run /spec-first. It produces specs/<feature>.md with Given/When/Then acceptance criteria and failing test files. It will not write implementation.
  2. Implement until the tests pass.
  3. Run /test-review. A fresh-context auditor reads the diff and reports tautologies, mocked-away logic, and missing edge cases. Fix findings.
  4. Run npm run test:all. Paste the output in the PR.
  5. Open the PR. CI is the final word.

8. Definition of Done (paste into every PR) ​

- [ ] Spec in specs/<feature>.md, written before implementation
- [ ] Unit tests for every new or changed core module
- [ ] Integration test for every new or changed adapter
- [ ] System test for the use case, if user-facing
- [ ] /test-review run, findings resolved
- [ ] npm run test:all output pasted below
- [ ] No forbidden patterns (section 5)
- [ ] Coverage and mutation thresholds unchanged or raised

9. Raising the bar ​

Thresholds only go up. Lowering a threshold in a project requires a commit that says why in the message body and a follow-up issue to restore it.

Jangkar. Tests live with the code; CI on the remote is the only authority.