test-schema

madooei's avatarfrom madooei

Write tests for Zod schemas. Use when testing entity schemas, DTO validation, query parameter schemas, or any schema validation rules. Triggers on "test schema", "schema tests", "test validation", "test zod".

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 2, 2026

When & Why to Use This Skill

This Claude skill automates the generation of comprehensive Vitest unit tests for Zod schemas. It streamlines the validation of backend entities, DTOs, and API query parameters, ensuring robust data integrity and type safety across TypeScript applications by following industry-standard testing patterns.

Use Cases

  • Generating unit tests for backend entity schemas to verify required fields, data types, and timestamp handling.
  • Testing Create and Update DTO (Data Transfer Object) validation rules, including field stripping and partial update logic.
  • Validating API query parameter schemas to ensure correct type coercion (e.g., strings to numbers) and valid filter/sort constraints.
  • Ensuring edge-case coverage for schema validation, such as empty strings, invalid enum values, and optional field behavior.
nametest-schema
descriptionWrite tests for Zod schemas. Use when testing entity schemas, DTO validation, query parameter schemas, or any schema validation rules. Triggers on "test schema", "schema tests", "test validation", "test zod".

Test Schema

Write Vitest tests for Zod schemas in this backend template.

Quick Reference

Location: tests/schemas/{entity-name}.schema.test.ts Run tests: pnpm test -- tests/schemas/{entity-name}.schema.test.ts Watch mode: pnpm test:watch

Instructions

Step 1: Create the Test File

Create tests/schemas/{entity-name}.schema.test.ts mirroring the source structure.

Step 2: Import Dependencies

import { describe, it, expect } from "vitest";
import {
  {entity}Schema,
  create{Entity}Schema,
  update{Entity}Schema,
  {entity}QueryParamsSchema,
} from "@/schemas/{entity-name}.schema";

Step 3: Test Each Schema

Create a describe block for each schema with focused test cases.

Test Patterns

Base Entity Schema Tests

describe("{entity}Schema", () => {
  it("accepts valid {entity}", () => {
    const valid = {
      id: "1",
      // ... all required fields with valid values
      createdBy: "user-1",
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    expect({entity}Schema.parse(valid)).toEqual(valid);
  });

  it("rejects missing required fields", () => {
    expect(() => {entity}Schema.parse({})).toThrow();
  });

  it("rejects invalid field types", () => {
    expect(() =>
      {entity}Schema.parse({
        id: 123, // should be string
        // ... other fields
      })
    ).toThrow();
  });

  it("accepts optional timestamps as undefined", () => {
    const valid = {
      id: "1",
      // ... required fields only, no timestamps
      createdBy: "user-1",
    };
    expect({entity}Schema.parse(valid)).toMatchObject({ id: "1" });
  });
});

Create DTO Schema Tests

describe("create{Entity}Schema", () => {
  it("accepts valid create data", () => {
    const valid = {
      // Only fields that user provides (no id, createdBy, timestamps)
      fieldName: "value",
    };
    expect(create{Entity}Schema.parse(valid)).toEqual(valid);
  });

  it("rejects empty required fields", () => {
    expect(() => create{Entity}Schema.parse({ fieldName: "" })).toThrow();
  });

  it("rejects missing required fields", () => {
    expect(() => create{Entity}Schema.parse({})).toThrow();
  });

  it("strips unknown fields", () => {
    const input = { fieldName: "value", unknownField: "ignored" };
    const parsed = create{Entity}Schema.parse(input);
    expect(parsed).not.toHaveProperty("unknownField");
  });
});

Update DTO Schema Tests

describe("update{Entity}Schema", () => {
  it("accepts partial update", () => {
    expect(update{Entity}Schema.parse({ fieldName: "updated" })).toEqual({
      fieldName: "updated",
    });
  });

  it("accepts empty object (no fields to update)", () => {
    expect(update{Entity}Schema.parse({})).toEqual({});
  });

  it("accepts multiple fields", () => {
    const update = { field1: "new1", field2: "new2" };
    expect(update{Entity}Schema.parse(update)).toEqual(update);
  });
});

Query Parameters Schema Tests

describe("{entity}QueryParamsSchema", () => {
  it("accepts empty object (all optional)", () => {
    expect({entity}QueryParamsSchema.parse({})).toEqual({});
  });

  it("coerces page and limit to numbers", () => {
    const parsed = {entity}QueryParamsSchema.parse({ page: "2", limit: "5" });
    expect(parsed.page).toBe(2);
    expect(parsed.limit).toBe(5);
  });

  it("accepts valid sortOrder values", () => {
    expect({entity}QueryParamsSchema.parse({ sortOrder: "asc" }).sortOrder).toBe("asc");
    expect({entity}QueryParamsSchema.parse({ sortOrder: "desc" }).sortOrder).toBe("desc");
  });

  it("rejects invalid sortOrder", () => {
    expect(() => {entity}QueryParamsSchema.parse({ sortOrder: "invalid" })).toThrow();
  });

  it("accepts entity-specific filters", () => {
    // Test any custom filters added to the query params
    expect({entity}QueryParamsSchema.parse({ createdBy: "user-1" }).createdBy).toBe("user-1");
  });
});

ID Schema Tests (if separate)

describe("{entity}IdSchema", () => {
  it("accepts valid string ID", () => {
    expect({entity}IdSchema.parse("abc-123")).toBe("abc-123");
  });

  it("rejects non-string ID", () => {
    expect(() => {entity}IdSchema.parse(123)).toThrow();
  });
});

Testing Guidelines

What to Test

  1. Happy path - Valid data passes validation
  2. Missing required fields - Schema rejects incomplete data
  3. Invalid types - Wrong data types are rejected
  4. Empty strings - Required strings with .min(1) reject empty
  5. Enum values - Only valid enum values accepted
  6. Coercion - Query params coerce strings to numbers
  7. Optional fields - Can be omitted without error
  8. Default values - Defaults are applied correctly

Test Naming Conventions

Use descriptive it statements:

  • "accepts valid {entity}" - Happy path
  • "rejects missing required fields" - Validation failure
  • "rejects invalid {field}" - Specific field validation
  • "coerces {field} to {type}" - Type coercion
  • "accepts optional {field} as undefined" - Optional handling
  • "applies default value for {field}" - Default behavior

Import Rules

  • Always use path aliases: import { x } from "@/schemas/..."
  • Import from vitest: describe, it, expect
  • Import z from zod only if testing generic schemas

Complete Example

See tests/schemas/note.schema.test.ts:

import { describe, it, expect } from "vitest";
import {
  noteSchema,
  createNoteSchema,
  updateNoteSchema,
  noteQueryParamsSchema,
} from "@/schemas/note.schema";

describe("noteSchema", () => {
  it("accepts valid note", () => {
    const valid = {
      id: "1",
      content: "Hello",
      createdBy: "user-1",
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    expect(noteSchema.parse(valid)).toEqual(valid);
  });

  it("rejects missing required fields", () => {
    expect(() => noteSchema.parse({})).toThrow();
  });
});

describe("createNoteSchema", () => {
  it("accepts valid create note", () => {
    expect(createNoteSchema.parse({ content: "Hi" })).toEqual({
      content: "Hi",
    });
  });

  it("rejects empty content", () => {
    expect(() => createNoteSchema.parse({ content: "" })).toThrow();
  });

  it("rejects missing content", () => {
    expect(() => createNoteSchema.parse({})).toThrow();
  });
});

describe("updateNoteSchema", () => {
  it("accepts partial update", () => {
    expect(updateNoteSchema.parse({ content: "Updated" })).toEqual({
      content: "Updated",
    });
  });

  it("accepts empty object (no update)", () => {
    expect(updateNoteSchema.parse({})).toEqual({});
  });
});

describe("noteQueryParamsSchema", () => {
  it("coerces page and limit to numbers", () => {
    const parsed = noteQueryParamsSchema.parse({ page: "2", limit: "5" });
    expect(parsed.page).toBe(2);
    expect(parsed.limit).toBe(5);
  });

  it("accepts optional createdBy", () => {
    expect(noteQueryParamsSchema.parse({ createdBy: "user-1" }).createdBy).toBe(
      "user-1",
    );
  });

  it("accepts empty object (all optional)", () => {
    expect(noteQueryParamsSchema.parse({})).toEqual({});
  });
});

What NOT to Do

  • Do NOT test implementation details - test behavior
  • Do NOT use snapshots for simple schema validation
  • Do NOT mock Zod - test actual schema parsing
  • Do NOT skip edge cases - empty strings, wrong types, missing fields
  • Do NOT forget to test coercion for query parameters