
test-schema
by madooei
This is a template for a backend service built with TypeScript and Hono.js. It provides a basic structure for creating RESTful APIs.
SKILL.md
name: test-schema description: 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".
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
- Happy path - Valid data passes validation
- Missing required fields - Schema rejects incomplete data
- Invalid types - Wrong data types are rejected
- Empty strings - Required strings with
.min(1)reject empty - Enum values - Only valid enum values accepted
- Coercion - Query params coerce strings to numbers
- Optional fields - Can be omitted without error
- 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
zfrom 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
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon