Claude-skill-registry integration-test-builder
Creates integration tests for API endpoints with database flows, including test harness setup, fixtures, setup/teardown, database seeding, and CI-friendly strategies. Use for "integration testing", "API tests", "database tests", or "test harness".
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/integration-test-builder" ~/.claude/skills/majiayu000-claude-skill-registry-integration-test-builder && rm -rf "$T"
manifest:
skills/data/integration-test-builder/SKILL.mdsource content
Integration Test Builder
Build comprehensive integration tests for APIs and database flows.
Test Harness Setup
// tests/setup/test-harness.ts import { PrismaClient } from "@prisma/client"; import { execSync } from "child_process"; export class TestHarness { prisma: PrismaClient; async setup() { // Setup test database process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; // Run migrations execSync("npx prisma migrate deploy"); // Initialize Prisma client this.prisma = new PrismaClient(); // Clear all data await this.clearDatabase(); } async teardown() { await this.prisma.$disconnect(); } async clearDatabase() { const tables = await this.prisma.$queryRaw<{ tablename: string }[]>` SELECT tablename FROM pg_tables WHERE schemaname = 'public' `; for (const { tablename } of tables) { if (tablename !== "_prisma_migrations") { await this.prisma.$executeRawUnsafe( `TRUNCATE TABLE "${tablename}" CASCADE` ); } } } async seedFixtures() { // Seed test data await this.prisma.user.create({ data: { email: "test@example.com", name: "Test User", }, }); } }
API Integration Tests
// tests/api/users.test.ts import request from "supertest"; import { app } from "@/app"; import { TestHarness } from "../setup/test-harness"; describe("User API", () => { let harness: TestHarness; beforeAll(async () => { harness = new TestHarness(); await harness.setup(); }); afterAll(async () => { await harness.teardown(); }); beforeEach(async () => { await harness.clearDatabase(); await harness.seedFixtures(); }); describe("POST /api/users", () => { it("should create new user", async () => { // Arrange const userData = { email: "new@example.com", name: "New User", }; // Act const response = await request(app) .post("/api/users") .send(userData) .expect(201); // Assert expect(response.body).toMatchObject({ email: userData.email, name: userData.name, }); expect(response.body.id).toBeDefined(); // Verify in database const user = await harness.prisma.user.findUnique({ where: { email: userData.email }, }); expect(user).toBeDefined(); expect(user!.name).toBe(userData.name); }); it("should return 400 for invalid email", async () => { // Arrange const userData = { email: "invalid-email", name: "Test User", }; // Act const response = await request(app) .post("/api/users") .send(userData) .expect(400); // Assert expect(response.body.error).toContain("Invalid email"); }); it("should return 409 for duplicate email", async () => { // Arrange const userData = { email: "test@example.com", // Already exists name: "Duplicate User", }; // Act const response = await request(app) .post("/api/users") .send(userData) .expect(409); // Assert expect(response.body.error).toContain("already exists"); }); }); describe("GET /api/users/:id", () => { it("should get user by id", async () => { // Arrange const user = await harness.prisma.user.findFirst(); // Act const response = await request(app) .get(`/api/users/${user!.id}`) .expect(200); // Assert expect(response.body).toMatchObject({ id: user!.id, email: user!.email, name: user!.name, }); }); it("should return 404 for non-existent user", async () => { // Act const response = await request(app).get("/api/users/99999").expect(404); // Assert expect(response.body.error).toContain("not found"); }); }); describe("PUT /api/users/:id", () => { it("should update user", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const updates = { name: "Updated Name" }; // Act const response = await request(app) .put(`/api/users/${user!.id}`) .send(updates) .expect(200); // Assert expect(response.body.name).toBe("Updated Name"); // Verify in database const updatedUser = await harness.prisma.user.findUnique({ where: { id: user!.id }, }); expect(updatedUser!.name).toBe("Updated Name"); }); }); describe("DELETE /api/users/:id", () => { it("should delete user", async () => { // Arrange const user = await harness.prisma.user.findFirst(); // Act await request(app).delete(`/api/users/${user!.id}`).expect(204); // Assert - verify deletion in database const deletedUser = await harness.prisma.user.findUnique({ where: { id: user!.id }, }); expect(deletedUser).toBeNull(); }); }); });
Database Transaction Tests
// tests/integration/order-flow.test.ts describe("Order Flow", () => { it("should create order with items in transaction", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const product = await harness.prisma.product.create({ data: { name: "Test Product", price: 99.99, stock: 10, }, }); const orderData = { userId: user!.id, items: [ { productId: product.id, quantity: 2, price: product.price, }, ], }; // Act const response = await request(app) .post("/api/orders") .send(orderData) .expect(201); // Assert const order = await harness.prisma.order.findUnique({ where: { id: response.body.id }, include: { items: true }, }); expect(order).toBeDefined(); expect(order!.items).toHaveLength(1); expect(order!.items[0].quantity).toBe(2); // Verify stock was decremented const updatedProduct = await harness.prisma.product.findUnique({ where: { id: product.id }, }); expect(updatedProduct!.stock).toBe(8); // 10 - 2 }); it("should rollback transaction if order creation fails", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const product = await harness.prisma.product.create({ data: { name: "Test Product", price: 99.99, stock: 1, // Only 1 in stock }, }); const orderData = { userId: user!.id, items: [ { productId: product.id, quantity: 10, // Requesting more than available price: product.price, }, ], }; // Act await request(app).post("/api/orders").send(orderData).expect(400); // Assert - verify rollback const orders = await harness.prisma.order.findMany(); expect(orders).toHaveLength(0); // Verify stock unchanged const unchangedProduct = await harness.prisma.product.findUnique({ where: { id: product.id }, }); expect(unchangedProduct!.stock).toBe(1); }); });
Authentication Tests
// tests/integration/auth.test.ts describe("Authentication", () => { describe("POST /api/auth/login", () => { it("should login with valid credentials", async () => { // Arrange await harness.prisma.user.create({ data: { email: "auth@example.com", password: await hash("password123"), }, }); // Act const response = await request(app) .post("/api/auth/login") .send({ email: "auth@example.com", password: "password123", }) .expect(200); // Assert expect(response.body.token).toBeDefined(); expect(response.body.user.email).toBe("auth@example.com"); }); it("should reject invalid password", async () => { // Act const response = await request(app) .post("/api/auth/login") .send({ email: "test@example.com", password: "wrong-password", }) .expect(401); // Assert expect(response.body.error).toContain("Invalid credentials"); }); }); describe("Protected routes", () => { let authToken: string; beforeEach(async () => { // Login to get token const response = await request(app).post("/api/auth/login").send({ email: "test@example.com", password: "password123", }); authToken = response.body.token; }); it("should access protected route with valid token", async () => { await request(app) .get("/api/profile") .set("Authorization", `Bearer ${authToken}`) .expect(200); }); it("should reject request without token", async () => { await request(app).get("/api/profile").expect(401); }); it("should reject request with invalid token", async () => { await request(app) .get("/api/profile") .set("Authorization", "Bearer invalid-token") .expect(401); }); }); });
Fixtures Management
// tests/fixtures/users.ts export const userFixtures = { admin: { email: "admin@example.com", name: "Admin User", role: "ADMIN", }, regularUser: { email: "user@example.com", name: "Regular User", role: "USER", }, testUser: { email: "test@example.com", name: "Test User", role: "USER", }, }; // tests/fixtures/products.ts export const productFixtures = { laptop: { name: "MacBook Pro", price: 2499.99, stock: 10, category: "Electronics", }, phone: { name: "iPhone 15", price: 999.99, stock: 50, category: "Electronics", }, }; // Usage in tests await harness.prisma.user.create({ data: userFixtures.admin, });
CI-Friendly Strategy
# .github/workflows/integration-tests.yml name: Integration Tests on: [push, pull_request] services: postgres: image: postgres:15 env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: test_db ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - run: npm ci - name: Run migrations run: npx prisma migrate deploy env: DATABASE_URL: postgresql://test:test@localhost:5432/test_db - name: Run integration tests run: npm run test:integration env: DATABASE_URL: postgresql://test:test@localhost:5432/test_db
Parallel Test Execution
// vitest.config.ts export default defineConfig({ test: { pool: "forks", poolOptions: { forks: { singleFork: false, // Run tests in parallel }, }, isolate: true, // Isolate each test file setupFiles: ["./tests/setup/global-setup.ts"], }, }); // Ensure each test file uses separate database const TEST_DB_PREFIX = "test_db_"; function getDatabaseUrl(): string { const workerId = process.env.VITEST_WORKER_ID || "1"; return `postgresql://test:test@localhost:5432/${TEST_DB_PREFIX}${workerId}`; }
Best Practices
- Isolated tests: Each test can run independently
- Clean state: Clear database between tests
- Fast fixtures: Minimal data seeding
- Transactions: Test rollbacks explicitly
- Real database: Don't mock database in integration tests
- CI-ready: Use Docker containers
- Parallel execution: Independent test databases
Output Checklist
- Test harness created
- Database setup/teardown
- Fixture management
- API endpoint tests
- Database transaction tests
- Authentication tests
- Error case coverage
- CI workflow configured
- Parallel execution support
- Clear test naming