Agent skill
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 this agent skill to your Project
npx add-skill https://github.com/patricio0312rev/skills/tree/main/testing/integration-test-builder
SKILL.md
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
rate-limiting-abuse-protection
Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
rbac-permissions-builder
Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
websocket-realtime-builder
Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications".
webhook-receiver-hardener
Secures webhook receivers with signature verification, retry handling, deduplication, idempotency keys, and error responses. Provides verification code, dedupe storage strategy, runbook for incidents. Use when implementing "webhooks", "webhook security", "event receivers", or "third-party integrations".
auth-module-builder
Implements secure authentication patterns including login/registration, session management, JWT tokens, password hashing, cookie settings, and CSRF protection. Provides auth routes, middleware, security configurations, and threat model documentation. Use when building "authentication", "login system", "JWT auth", or "session management".
rest-to-graphql-migrator
Migrates REST APIs to GraphQL incrementally with schema stitching, REST datasources, and gradual endpoint migration. Use when users request "migrate to GraphQL", "REST to GraphQL", "GraphQL wrapper", or "API modernization".
Didn't find tool you were looking for?