Back to skills
SkillHub ClubShip Full StackFull StackTesting

testing-zod-schemas

Imported from https://github.com/djankies/claude-configs.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
0
Hot score
74
Updated
March 20, 2026
Overall rating
C2.6
Composite score
2.6
Best-practice grade
B84.0

Install command

npx @skill-hub/cli install djankies-claude-configs-testing-zod-schemas

Repository

djankies/claude-configs

Skill path: zod-4/skills/testing-zod-schemas

Imported from https://github.com/djankies/claude-configs.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Testing.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: djankies.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install testing-zod-schemas into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/djankies/claude-configs before adding testing-zod-schemas to shared team environments
  • Use testing-zod-schemas for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: testing-zod-schemas
description: Test Zod schemas comprehensively with unit tests, integration tests, and type tests for validation logic
---

# Testing Zod Schemas

## Purpose

Comprehensive guide to testing Zod v4 schemas, including validation logic, error messages, transformations, and type inference.

**For Vitest test structure, mocking, and async patterns, use `vitest-4/skills/writing-vitest-tests`**

## Unit Testing Schemas

### Basic Validation Tests

```typescript
import { z } from 'zod';

const userSchema = z.object({
  email: z.email().trim().toLowerCase(),
  age: z.number().min(18),
  username: z.string().trim().min(3)
});

const result = userSchema.safeParse({
  email: '[email protected]',
  age: 25,
  username: 'john'
});

expect(result.success).toBe(true);
if (result.success) {
  expect(result.data.email).toBe('[email protected]');
}

const invalidResult = userSchema.safeParse({
  email: 'not-an-email',
  age: 25,
  username: 'john'
});

expect(invalidResult.success).toBe(false);
if (!invalidResult.success) {
  expect(invalidResult.error.issues[0].path).toEqual(['email']);
}
```

### Testing Transformations

```typescript
const emailSchema = z.email().trim().toLowerCase();

const result = emailSchema.safeParse('  [email protected]  ');

expect(result.success).toBe(true);
if (result.success) {
  expect(result.data).toBe('[email protected]');
}
```

### Testing Error Messages

```typescript
const schema = z.object({
  email: z.email({ error: "Please enter a valid email address" }),
  password: z.string().min(8, {
    error: "Password must be at least 8 characters"
  })
});

const result = schema.safeParse({
  email: 'invalid',
  password: 'password123'
});

expect(result.success).toBe(false);
if (!result.success) {
  expect(result.error.issues[0].message).toBe(
    "Please enter a valid email address"
  );
}
```

### Testing Refinements

```typescript
const passwordSchema = z.string()
  .min(8)
  .refine(
    (password) => /[A-Z]/.test(password),
    { error: "Must contain uppercase letter" }
  )
  .refine(
    (password) => /[0-9]/.test(password),
    { error: "Must contain number" }
  );

const validResult = passwordSchema.safeParse('Password123');
expect(validResult.success).toBe(true);

const invalidResult = passwordSchema.safeParse('password123');
expect(invalidResult.success).toBe(false);
if (!invalidResult.success) {
  expect(invalidResult.error.issues[0].message).toBe(
    "Must contain uppercase letter"
  );
}
```

### Testing Async Refinements

```typescript
const emailSchema = z.email().refine(
  async (email) => {
    const exists = await checkEmailExists(email);
    return !exists;
  },
  { error: "Email already exists" }
);

const validResult = await emailSchema.safeParseAsync('[email protected]');
expect(validResult.success).toBe(true);

const invalidResult = await emailSchema.safeParseAsync('[email protected]');
expect(invalidResult.success).toBe(false);
if (!invalidResult.success) {
  expect(invalidResult.error.issues[0].message).toBe("Email already exists");
}
```

## Testing Complex Schemas

### Nested Objects

```typescript
const addressSchema = z.object({
  street: z.string().trim().min(1),
  city: z.string().trim().min(1),
  zip: z.string().trim().regex(/^\d{5}$/)
});

const userSchema = z.object({
  name: z.string().trim().min(1),
  address: addressSchema
});

const result = userSchema.safeParse({
  name: 'John',
  address: { street: '123 Main St', city: 'Boston', zip: 'invalid' }
});

expect(result.success).toBe(false);
if (!result.success) {
  expect(result.error.issues[0].path).toEqual(['address', 'zip']);
}
```

### Arrays

```typescript
const tagsSchema = z.array(
  z.string().trim().min(1)
).min(1, { error: "At least one tag required" });

const result = tagsSchema.safeParse(['valid', '']);

expect(result.success).toBe(false);
if (!result.success) {
  expect(result.error.issues[0].path).toEqual([1]);
}
```

### Discriminated Unions

```typescript
const eventSchema = z.discriminatedUnion('type', [
  z.object({
    type: z.literal('click'),
    x: z.number(),
    y: z.number()
  }),
  z.object({
    type: z.literal('keypress'),
    key: z.string()
  })
]);

const result = eventSchema.safeParse({
  type: 'click',
  x: 100,
  y: 200
});

expect(result.success).toBe(true);
```

## Type Testing

### Type Inference

```typescript
const userSchema = z.object({
  email: z.email(),
  age: z.number(),
  name: z.string()
});

type User = z.infer<typeof userSchema>;

expectTypeOf<User>().toEqualTypeOf<{
  email: string;
  age: number;
  name: string;
}>();
```

### Transform Types

```typescript
const schema = z.string().transform(s => parseInt(s));

type Input = z.input<typeof schema>;
type Output = z.output<typeof schema>;

expectTypeOf<Input>().toEqualTypeOf<string>();
expectTypeOf<Output>().toEqualTypeOf<number>();
```

## Best Practices

### 1. Test Both Success and Failure

Always test valid data passes and invalid data fails

### 2. Test Transformations

Verify trim, lowercase, and other transforms produce expected output

### 3. Verify Error Messages

Check custom error messages appear correctly

### 4. Test Edge Cases

Handle empty strings, very long strings, special characters

### 5. Use SafeParse in Tests

```typescript
const result = schema.safeParse(data);  // ✅
try { schema.parse(data) }              // ❌
```

### 6. Test Type Inference

Verify `z.infer`, `z.input`, and `z.output` produce correct types

## Test Coverage

Aim for:
- **100% branch coverage** for validation logic
- **100% path coverage** for refinements
- **Edge cases** tested thoroughly
- **Error messages** verified
- **Transformations** validated

For coverage configuration in Vitest 4.x when testing schemas, use vitest-4/skills/configuring-vitest-4 for coverage include patterns and thresholds setup.

## References

- v4 Features: Use the validating-string-formats skill from the zod-4 plugin
- Error handling: Use the customizing-errors skill from the zod-4 plugin
- Transformations: Use the transforming-string-methods skill from the zod-4 plugin
- Performance: Use the optimizing-performance skill from the zod-4 plugin

**Cross-Plugin References:**

- If testing Zod validation with React components, use the testing-components skill for component integration testing patterns
- [@vitest-4/skills/configuring-vitest-4](/vitest-4/skills/configuring-vitest-4/SKILL.md) - Coverage configuration for schema testing

## Success Criteria

- ✅ 100% branch coverage for validation logic
- ✅ Success and failure paths tested
- ✅ Transformations verified
- ✅ Error messages validated
- ✅ Edge cases covered
- ✅ Type inference tested
- ✅ Integration tests pass
- ✅ Performance benchmarks meet targets
testing-zod-schemas | SkillHub