| name | rule-testing |
| description | Vitest patterns, 100% coverage thresholds, and fixture-driven schema validation |
Rule testing
Apply this rule whenever work touches:
src/**/*.spec.ts
src/test-utils/**
Testing guide for the schemas package
The schemas package enforces 100% test coverage. Every schema, utility, and export must be thoroughly tested because this is a published library — bugs propagate to every consumer.
Test file organization
Tests live in __tests__/ directories adjacent to the source code they test:
src/
mass-id/
__tests__/
mass-id.data.schema.spec.ts
mass-id.schema.spec.ts
mass-id.data.schema.ts
mass-id.schema.ts
shared/
schemas/
primitives/
__tests__/
ids.schema.spec.ts
ids.schema.ts
Always use the .spec.ts extension. Never use .test.ts — the project is configured for .spec.ts only.
Coverage requirements
Coverage thresholds are enforced at 100% for all metrics:
- Branches: 100%
- Functions: 100%
- Lines: 100%
- Statements: 100%
If coverage drops, add tests. Never reduce thresholds or delete tests to meet them.
Fixture patterns
Use centralized fixtures from src/test-utils/fixtures/ for valid data. Fixtures follow these conventions:
Fixture suffix for valid baseline data
export const MassIDDataFixture: MassIDData = {
type: 'Mass ID',
name: 'Test Mass ID',
weight_kg: 1500,
};
Factory functions for variants
When tests need variations of valid data, use factory functions:
export function createMassIDDataFixture(
overrides?: Partial<MassIDData>,
): MassIDData {
return {
...MassIDDataFixture,
...overrides,
};
}
Check for existing fixtures
Before creating local test data, check src/test-utils/fixtures/ for existing fixtures. Import and override rather than duplicating.
Using .safeParse() for validation tests
Always use .safeParse() instead of .parse() in tests. This allows explicit assertions on both success and failure cases without try/catch.
it('should accept valid mass-id data', () => {
const result = MassIDDataSchema.safeParse(MassIDDataFixture);
expect(result.success).toBe(true);
expect(result.data).toEqual(MassIDDataFixture);
});
it('should reject missing required field', () => {
const { name, ...withoutName } = MassIDDataFixture;
const result = MassIDDataSchema.safeParse(withoutName);
expect(result.success).toBe(false);
expect(result.error?.issues).toEqual(
expect.arrayContaining([expect.objectContaining({ path: ['name'] })]),
);
});
Testing both valid and invalid inputs
Every schema test suite must cover:
- Valid complete input — all fields present with correct types
- Valid minimal input — only required fields (optional fields omitted)
- Invalid types — wrong types for each field
- Missing required fields — each required field omitted
- Extra properties — strict object rejects unknown fields
- Boundary values — min/max lengths, empty strings, zero, negative numbers
- Optional field presence — optional fields accepted when present, accepted when absent
describe('LocationSchema', () => {
it('should accept valid complete location', () => { ... });
it('should accept location with only required fields', () => { ... });
it('should reject invalid latitude type', () => { ... });
it('should reject missing longitude', () => { ... });
it('should reject extra properties', () => { ... });
it('should reject latitude outside range [-90, 90]', () => { ... });
it('should accept optional description when present', () => { ... });
});
Table-driven tests with it.each
Use it.each for testing multiple inputs against the same assertion. This is especially useful for enum validations, boundary checks, and field type validations.
it.each([
['valid-uuid', true],
['not-a-uuid', false],
['', false],
['123e4567-e89b-12d3-a456-426614174000', true],
])('should validate UUID "%s" as %s', (input, expected) => {
const result = UuidSchema.safeParse(input);
expect(result.success).toBe(expected);
});
it.each([
['None', true],
['Low', true],
['Medium', true],
['High', true],
['none', false],
['NONE', false],
['Invalid', false],
])('should validate contamination level "%s" as %s', (input, expected) => {
const result = ContaminationLevelSchema.safeParse(input);
expect(result.success).toBe(expected);
});
Testing type inference
Verify that Zod's inferred types match expected TypeScript types using expectTypeOf from Vitest:
import { expectTypeOf } from 'vitest';
it('should infer correct type from MassIDDataSchema', () => {
type Inferred = z.infer<typeof MassIDDataSchema>;
expectTypeOf<Inferred>().toMatchTypeOf<MassIDData>();
});
Test structure
Follow the Arrange-Act-Assert pattern:
it('should reject weight below minimum', () => {
const input = createMassIDDataFixture({ weight_kg: -1 });
const result = MassIDDataSchema.safeParse(input);
expect(result.success).toBe(false);
});
Prohibited patterns
- No
.only — committed tests must never use it.only or describe.only
- No mocking Zod — test schemas as black boxes; never mock Zod internals
- No
expect(true).toBe(true) — always assert meaningful values
- No test interdependence — each test must be independently runnable
- No snapshot testing for schemas — assert specific fields and values explicitly