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".
Works with
---
name: integration-test-builder
description: 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".
license: MIT
---
# Integration Test Builder
Build comprehensive integration tests for APIs and database flows.
## Test Harness Setup
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```yaml
# .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
```typescript
// 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
1. **Isolated tests**: Each test can run independently
2. **Clean state**: Clear database between tests
3. **Fast fixtures**: Minimal data seeding
4. **Transactions**: Test rollbacks explicitly
5. **Real database**: Don't mock database in integration tests
6. **CI-ready**: Use Docker containers
7. **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 namingMore Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

