| name | unit-test |
| description | Use when writing or updating tests for schemas, especially when 100% coverage is required |
Write and maintain Vitest tests for Zod schemas in the schemas package, targeting 100% coverage on all four thresholds.
Test File Location
Tests live alongside their source in __tests__/ directories:
src/
{type}/
{type}.schema.ts
{type}.data.schema.ts
__tests__/
{type}.schema.spec.ts # Tests for all schemas in this type
The naming convention is {type}.schema.spec.ts matching the main schema file.
Test Structure
Every test file follows this structure:
import { describe, expect, it } from 'vitest';
import { {TypeName}Schema } from '../{type}.schema';
import { {TypeName}DataSchema } from '../{type}.data.schema';
describe('{TypeName}Schema', () => {
describe('valid inputs', () => {
});
describe('invalid inputs', () => {
});
describe('optional fields', () => {
});
describe('edge cases', () => {
});
});
Using .safeParse() for Validation
Always use .safeParse() instead of .parse() in tests. This returns a result object instead of throwing:
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
expect(() => Schema.parse(input)).not.toThrow();
To inspect parse errors during debugging:
const result = Schema.safeParse(input);
if (!result.success) {
console.log(JSON.stringify(result.error.issues, null, 2));
}
expect(result.success).toBe(true);
Testing Valid Inputs
Provide at least one complete valid input with all required fields:
it('should parse a complete valid input', () => {
const input = {
name: 'Test Document',
description: 'A test document for validation',
image: 'https://example.com/image.png',
external_url: 'https://example.com',
data: {
field_name: 'value',
numeric_field: 42,
},
attributes: [{ trait_type: 'Category', value: 'Test' }],
};
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
});
Use it.each for multiple valid variations:
it.each([
[
'with minimum required fields',
{
},
],
[
'with all optional fields',
{
},
],
[
'with different valid enum value',
{
},
],
])('should parse valid input: %s', (_label, input) => {
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
});
Testing Invalid Inputs
Test each required field individually for rejection:
it('should reject when name is missing', () => {
const { name, ...input } = validInput;
const result = Schema.safeParse(input);
expect(result.success).toBe(false);
});
it('should reject when name is wrong type', () => {
const input = { ...validInput, name: 123 };
const result = Schema.safeParse(input);
expect(result.success).toBe(false);
});
Test unknown property rejection (strictObject enforcement):
it('should reject unknown properties', () => {
const input = {
...validInput,
unknown_field: 'should not be here',
};
const result = Schema.safeParse(input);
expect(result.success).toBe(false);
});
Table-driven invalid input tests:
it.each([
['missing name', { ...validInput, name: undefined }],
['empty string name', { ...validInput, name: '' }],
['numeric name', { ...validInput, name: 123 }],
[
'negative amount',
{ ...validInput, data: { ...validInput.data, amount: -1 } },
],
[
'invalid enum value',
{ ...validInput, data: { ...validInput.data, status: 'invalid' } },
],
])('should reject invalid input: %s', (_label, input) => {
const result = Schema.safeParse(input);
expect(result.success).toBe(false);
});
Testing Optional Fields
Verify schemas parse correctly with and without optional fields:
describe('optional fields', () => {
it('should parse without optional description', () => {
const { description, ...input } = validInput;
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
});
it('should parse with optional description present', () => {
const input = { ...validInput, description: 'Optional value' };
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.description).toBe('Optional value');
}
});
});
Testing Edge Cases
Cover boundary values and unusual but valid inputs:
describe('edge cases', () => {
it('should handle maximum length strings', () => {
const input = { ...validInput, name: 'a'.repeat(1000) };
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
});
it('should reject zero when field uses .positive()', () => {
const input = { ...validInput, data: { ...validInput.data, amount: 0 } };
const result = Schema.safeParse(input);
expect(result.success).toBe(false);
});
it('should accept zero when field uses .nonnegative()', () => {
const input = { ...validInput, data: { ...validInput.data, amount: 0 } };
const result = Schema.safeParse(input);
expect(result.success).toBe(true);
});
it(, {
input = { ...validInput, : [] };
result = .(input);
(result.).();
});
(, {
input = { ...validInput, : [] };
result = .(input);
(result.).();
(result.) {
(result..).([]);
}
});
(, {
input = { ...validInput, : };
result = .(input);
(result.).();
});
});
Using Centralized Fixtures
Check src/test-utils/fixtures/ for reusable test data before creating local fixtures:
import { validNftInput } from '../../test-utils/fixtures/nft-fixtures';
const validInput = {
...validNftInput,
data: {
field_name: 'type-specific-value',
},
};
Running Tests with Coverage
Run tests with coverage to verify 100% thresholds:
pnpm test:coverage
npx vitest run src/mass-id/__tests__/mass-id.schema.spec.ts
npx vitest run --reporter=verbose
pnpm test:watch
Coverage Thresholds
The schemas package enforces 100% on all four coverage metrics:
| Metric | Threshold | What It Measures |
|---|
| Statements | 100% | Every executable statement is reached |
| Branches | 100% | Every if/else, ternary, and optional chain is covered |
| Functions | 100% | Every function/method is called |
| Lines | 100% | Every line of code is executed |
If coverage drops below 100%, identify uncovered lines:
pnpm test:coverage
Common Coverage Gaps
| Gap | Solution |
|---|
| Unreachable default export | Ensure barrel exports are tested indirectly |
| Untested enum value | Add an it.each case for each enum value |
| Untested optional branch | Add a test without the optional field |
| Untested error path | Add an invalid input that triggers the error |
Anti-Patterns to Avoid
- No
.only: Never commit it.only or describe.only (lint rule enforced)
- No snapshot tests: Use explicit assertions, not
toMatchSnapshot()
- No
parse() in tests: Always use safeParse() for predictable behavior
- No
expect(true).toBe(true): Always assert on meaningful values
- No skipped tests: Don't commit
it.skip or xit