From typescript-services
Enforces test code standards for unit/integration/e2e tests in test/ only (NOT src/). Use when writing tests, generating test files, or implementing test helpers/mocks under test/.
npx claudepluginhub andercore-labs/claudes-kitchen --plugin typescript-servicesThis skill uses the workspace's default tool permissions.
Test code under `test/` only. Production code has separate standards.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Monitors deployed URLs for regressions in HTTP status, console errors, performance metrics, content, network, and APIs after deploys, merges, or upgrades.
Provides React and Next.js patterns for component composition, compound components, state management, data fetching, performance optimization, forms, routing, and accessible UIs.
Test code under test/ only. Production code has separate standards.
For Result/Task/Maybe → Skill(recipes:using-true-myth-recipe)
Correct assertions:
expect(result).toEqual(Result.ok(exactData))
expect(result).toEqual(Result.err(new SpecificError('expected message')))
expect(result).toEqual(Result.err(expect.objectContaining({ message: 'expected' })))
Forbidden assertions:
expect(result.isErr).toBe(true)
expect(result.isOk).toBe(true)
expect(result).toMatchObject({ variant: 'Ok' })
result.match({ Ok: ..., Err: ... })
expect(result).toEqual(Result.err(expect.any(Error)))
expect(result).toEqual(Result.err(expect.any(ErrorType)))
See examples.md for complete test patterns.
Test generation | unit tests | integration tests | e2e tests | test helpers | mocks
| Type | Location | Scope | I/O | Speed | Errors |
|---|---|---|---|---|---|
| Unit | test/domain/, test/service/ | Pure isolation | Mock all | <100ms | Mock errors |
| Integration | test/inbound/, test/outbound/ | Boundary contracts | Real SDK/client | Fast | ALL error scenarios |
| E2E | test/e2e/ | Smoke tests | Real services | Slower | Happy path ONLY |
| Pattern | Status |
|---|---|
| Network calls | ✗ |
| File system | ✗ |
| Real databases | ✗ |
| setTimeout/delay/sleep | ✗ |
| Fast (<100ms) | ✓ |
| Deterministic | ✓ |
| Mock all dependencies | ✓ |
| Test behaviour not implementation | ✓ |
Boundary testing, adapters, ALL ERROR SCENARIOS
| Pattern | Status |
|---|---|
| Test real adapters (NO mocks for SDK/client) | ✓ |
| ALL documented error codes/responses | ✓ |
| Reset state between tests | ✓ |
| No arbitrary waits (use proper async/await) | ✓ |
Error testing (MANDATORY):
| Scenario | Status |
|---|---|
| Invalid inputs | ✓ |
| Auth/authz failures | ✓ |
| External service failures (5xx) | ✓ |
| Rate limiting | ✓ |
| Network failures | ✓ |
| ALL HTTP status codes | ✓ |
Happy path smoke tests ONLY
| Pattern | Status |
|---|---|
| Error scenarios | ✗ |
| Edge cases | ✗ |
| Failure paths | ✗ |
| Invalid inputs | ✗ |
| Successful user journeys ONLY | ✓ |
| Observable outcomes (API, DB, state) | ✓ |
| Clean setup/teardown | ✓ |
| Idempotent tests | ✓ |
| Helper Type | Returns | Error Handling | Example |
|---|---|---|---|
| Creation | Concrete type | Throw on invalid | createTestUser(name) → throw if !name |
| Mock (mimics prod) | Result/Task/Maybe | Return monadic | mockFind() → Task<User, Error> |
| Pattern | Status | Reason |
|---|---|---|
expect(result).toEqual(Result.ok(exactData)) | ✓ | Direct equality with exact data |
expect(result).toEqual(Result.err(new SpecificError('msg'))) | ✓ | Exact error type and message |
expect(result).toEqual(Result.err(expect.objectContaining({message: 'msg'}))) | ✓ | Specific error with partial match |
expect(result).toEqual(Result.ok(expect.objectContaining({field: value}))) | ✓ | Partial data verification |
expect(mock).toHaveBeenCalledWith(args) | ✓ | Verify interactions |
expect(result).toEqual(Result.err(expect.any(Error))) | ✗ CRITICAL | No generic error matching - use specific error type |
expect(result).toEqual(Result.err(expect.any(ErrorType))) | ✗ CRITICAL | No generic error matching - use specific error type |
expect(result.isErr).toBe(true) | ✗ CRITICAL | Checks internal property |
expect(result.isOk).toBe(true) | ✗ CRITICAL | Checks internal property |
expect(result).toMatchObject({ variant: 'Ok' }) | ✗ CRITICAL | Checks internal structure |
expect(result).toMatchObject({ variant: 'Err' }) | ✗ CRITICAL | Checks internal structure |
result.match({ Ok: ..., Err: ... }) | ✗ CRITICAL | No match in assertions |
result.unwrap() | ✗ CRITICAL | Can throw - unsafe |
if (result.isOk) expect(...) | ✗ CRITICAL | No conditionals in assertions |
result.value | ✗ CRITICAL | No direct value access |
| Context | Rule |
|---|---|
| Test helpers | NO null/undefined - use Maybe |
| Mock returns | [] → empty arrays, {} → empty objects |
| Optional values | Maybe not T | null |
| Collections | User[] never User[] | null |
| No-value operations | Result.ok(Unit) not Result.ok(undefined) |
| Folder | Suffix | Forbidden |
|---|---|---|
| test/domain/* | .entity.spec.ts | .domain.spec.ts, .model.spec.ts |
| test/service/* | .service.spec.ts | .usecase.spec.ts |
| test/inbound/* | .controller.spec.ts, .consumer.spec.ts, .guard.spec.ts | .handler.spec.ts, .middleware.spec.ts |
| test/outbound/* | .adapter.integration.spec.ts | .client.spec.ts, .sdk.spec.ts |
| test/helpers/ | .helper.ts (NO .spec) | .util.ts, .utils.ts |
| test/mocks/ | .mock.ts (NO .spec) | .stub.ts, .fake.ts |
| Element | Convention |
|---|---|
| Files | kebab-case.spec.ts |
| Classes | PascalCase |
| Types | PascalCase |
| Functions | camelCase |
| Constants | UPPER_SNAKE_CASE |
| describe() | Behaviour-focused (class/component name) |
| it() | Should statements |
| Pattern | Status |
|---|---|
| Simple fake implementations | ✓ |
| Jest mocks for boundaries | ✓ |
| Deep mock chains | ✗ |
| Over-mocking internals | ✗ |
| Reset mocks between tests | ✓ |
| Rule | Status |
|---|---|
| Direct process.env in tests | ✓ |
| Deterministic test data | ✓ |
| Random values without seeds | ✗ |
| Production secrets | ✗ |
| Pattern | Status |
|---|---|
| console.log for debugging | ✓ |
| Remove excessive logs before commit | ✓ |
| Test output for failures | ✓ |
| Production logging patterns | ✗ |
0 test files → Create concrete test
1 test file → Keep inline, no extraction
2+ duplicating → Extract to test/helpers
Common mocks → test/mocks directory
Shared fixtures → Co-locate with tests
AFTER ANY TEST CHANGE:
pnpm lint
pnpm test
MUST:
| Never Allowed | Use Instead |
|---|---|
| //, /* */, TODO/FIXME | Self-documenting code |
| @ts-ignore, eslint-disable | Fix properly |
| interface | type |
| any | Specific type |
| enum | Union types |
| default export, export * | Named exports |
| it.skip, describe.skip | Delete skipped tests |
| null/undefined | Maybe, [], {} |
| Nested monads | Result<T, NotFoundError> not Result<Maybe> |
| expect.any(Error), expect.any(ErrorType) | new SpecificError('message') or expect.objectContaining({message: 'text'}) |
| result.isErr, result.isOk | expect(result).toEqual(Result.err(new SpecificError('msg'))) |
| toMatchObject({ variant: 'Ok'|'Err' }) | expect(result).toEqual(Result.ok(data)) |
| result.match() in assertions | expect(result).toEqual(Result.ok(data)) |
| result.unwrap() | expect(result).toEqual(Result.ok(data)) |
| Pattern | Status | Note |
|---|---|---|
| let in Jest setup blocks | ✓ | beforeEach/afterEach only |
| Task.resolve() for test data | ✓ | Test helpers only |
| let in test body | ✗ | Use const |
| for/while loops | ✗ | Use map/filter/reduce |
| mutations | ✗ | Use spread operators |
| Test Type | Target | Focus |
|---|---|---|
| Unit | 100% | Every edge case (10-20 tests per class) |
| Integration | ALL errors | Error scenarios, boundaries (5-10 per adapter) |
| E2E | Happy paths | Success journeys ONLY (1-3 per flow) |
Error delegation:
ALL error cases → Integration tests
Invalid inputs → Integration (inbound adapters)
External failures → Integration (outbound adapters)
E2E → NEVER test errors
| Item | Limit |
|---|---|
| Function | ≤20 lines |
| File | ≤500 lines |
| Class | ≤10 methods |
| Parameters | ≤5 per function |
| Duplication | Extract if ≥3 lines repeated |
| Suites | One per file |
CHECK:
1. Absolute prohibitions → NO comments/interface/skip/null/expect.any(Error)/expect.any(ErrorType)/result.isErr/result.isOk/toMatchObject({ variant })/result.match()
2. Test location → Under test/ directory
3. Helper patterns → Creation throws, mock returns monadic
4. Type safety → No any/interface/null/undefined
5. Null/undefined → Strict: Use Maybe<T>, [], {}
6. Nested monads → NO Result<Maybe<T>>, Task<Maybe<T>>
7. Naming → Correct .spec.ts suffixes
8. Assertions → ONLY Result.ok(exactData)/Result.err(new SpecificError('msg')) equality
9. Forbidden assertion patterns (CRITICAL):
- NO expect.any(Error) or expect.any(ErrorType) - use specific error type and message
- NO result.isErr/.isOk (internal property access)
- NO toMatchObject({ variant: 'Ok'/'Err' })
- NO result.match({ Ok: ..., Err: ... })
- NO result.unwrap()
10. Mock interactions → Assert calls with correct args
11. let usage → ONLY in Jest setup blocks
12. Collections → [] or {} for empty, never null
13. Run verification → pnpm lint && pnpm test (MUST pass)
14. Test architecture → Unit/integration/e2e boundaries correct
15. Error assertions → Specific error type with message validation (NO expect.any)
16. All pass → Generate code
ANY fail → REJECT with violation
ALL rules satisfied:
→ Emit code only (no comments, no prose)
ANY rule violated:
→ REJECT: <specific violation>
→ Specify exact line/pattern that violates
→ Suggest functional alternative
→ NO partial code
See test-violations-guide.md for comprehensive violation patterns and fixes.
MANDATORY: Run after generating or reviewing test code.
| Phase | Action |
|---|---|
| 1. Scan | Identify test files in test/ |
| 2. Validate | Run ALL 16 verification protocol checks + test architecture + assertion quality → gather violations |
| 3. Report | ✓ ALL pass → Done | ✗ ANY fail → REJECT with violations |
| 4. Fix | Violations → Regenerate → Re-validate |
| 5. Store Metrics | After ALL validation passes → call mcp__agent-orchestrator__store-skill-metrics |
Run ALL 16 checks from Verification Protocol, plus:
Test Architecture
| Pattern | Severity | Fix |
|---|---|---|
| Adapter unit test mocked SDK happy path | WARNING | Integration with Testcontainers |
| Adapter unit test verifies field mapping | WARNING | Integration validates |
| Adapter unit mocking SDK without error scenarios | WARNING | Unit only for unreproducible errors |
| Integration test in test/service/ | ERROR | Move to test/outbound/ |
| Integration mocks MongoDB, Kafka, Redis | ERROR | Use Testcontainers for real |
| Integration connects to shared test DB/broker | ERROR | Testcontainers for isolation |
| Integration no cleanup in afterEach/afterAll | ERROR | Add cleanup |
| Integration depends on execution order | ERROR | Each test setup own data |
| Controller test calls methods directly | WARNING | Use supertest through HTTP |
| Controller test mocks service layer | ERROR | Real services with Testcontainers |
Testing Strategy:
| Infrastructure Type | Pattern |
|---|---|
| OWN_INFRA (MongoDB, Kafka, Redis) | Testcontainers - NO mocks, NO shared |
| INTERNAL_SERVICE_DEPS (microservices) | Staging environment - NOT mocks unless unavailable |
| EXTERNAL_SERVICE_DEPS (3rd party APIs) | Real service sandbox/test - mock server if unavailable/cost-prohibitive |
Assertion Quality:
| Pattern | Severity | Fix |
|---|---|---|
| expect(result).toEqual(Result.err(expect.any(Error))) | CRITICAL | Use specific error: Result.err(new SpecificError('expected message')) |
| expect(result).toEqual(Result.err(expect.any(ErrorType))) | CRITICAL | Use specific error type and message validation |
| expect(result).toMatchObject({ variant: 'Ok' }) | CRITICAL | Add expect.objectContaining({ ...actualData }) or toEqual(Result.ok(expectedValue)) |
| Testing monad type without data verification | CRITICAL | Verify actual fields: sessionId, totalStored, etc. |
| Shallow assertions on Result/Task/Maybe | CRITICAL | Assert on wrapped value with toEqual(Result.ok(...)) |
All pass:
{
"discipline": "test-code",
"timestamp": "ISO8601",
"helper_patterns": {"violations": [], "status": "pass"},
"assertions": {"violations": [], "status": "pass"},
"type_safety": {"violations": [], "status": "pass"},
"null_handling": {"violations": [], "status": "pass"},
"naming_conventions": {"violations": [], "status": "pass"},
"prohibitions": {"violations": [], "status": "pass"},
"test_architecture": {"violations": [], "status": "pass"},
"summary": {"critical": 0, "errors": 0, "warnings": 0}
}
Violations found (REJECT):
{
"discipline": "test-code",
"timestamp": "ISO8601",
"assertions": {
"violations": [
{"file": "test/service/user.service.spec.ts", "line": 25, "severity": "critical", "rule": "unwrap_in_test", "violation": "result.unwrap() used", "fix": "expect(result).toEqual(Result.ok(user))"}
],
"status": "fail"
},
"test_architecture": {
"violations": [
{"file": "test/outbound/mongo.adapter.integration.spec.ts", "line": 10, "severity": "error", "rule": "mocked_infrastructure", "violation": "Mocks MongoDB instead of Testcontainers", "fix": "Use Testcontainers for real MongoDB instance"}
],
"status": "fail"
},
"summary": {"critical": 1, "errors": 1, "warnings": 0}
}
Review generated/modified test code → Gather evidence → Cite file:line
NOT: Re-execute OR invent violations
Re-validation required after fixes. Repeat until ALL checks pass.
See true-myth-recipe for Task/Result/Maybe patterns