Analyze existing systems and generate comprehensive integration and end-to-end tests to verify component interactions and user flows. This skill is for **retrofitting tests onto existing codebases**, not TDD (where tests are written first).
/plugin marketplace add marcel-Ngan/ai-dev-team/plugin install marcel-ngan-ai-dev-team@marcel-Ngan/ai-dev-teamThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Analyze existing systems and generate comprehensive integration and end-to-end tests to verify component interactions and user flows. This skill is for retrofitting tests onto existing codebases, not TDD (where tests are written first).
| Scenario | Skill to Use | Tests Written |
|---|---|---|
| TDD (new code) | skills/testing/integration | BEFORE implementation |
| Retrofit (existing code) | skills/testing/add-integration-tests | AFTER code exists |
Test interactions between components within the system:
| Type | Scope | Example |
|---|---|---|
| API | HTTP endpoints | REST/GraphQL endpoint testing |
| Database | Data persistence | Repository CRUD operations |
| Service | Service layer | Multi-component workflows |
| Message | Event/queue systems | Pub/sub, message queues |
Test complete user flows through the entire system:
| Type | Scope | Example |
|---|---|---|
| User Journey | Full flow | Login → Browse → Purchase → Logout |
| Critical Path | Key business flows | Checkout, registration, payment |
| Cross-System | Multiple services | Frontend → API → Database → External |
## System Analysis Checklist
### Architecture
- [ ] Identify entry points (APIs, UI, queues)
- [ ] Map service dependencies
- [ ] Identify database interactions
- [ ] Note external service integrations
- [ ] Document authentication/authorization flow
### Data Flow
- [ ] Trace request paths through system
- [ ] Identify data transformations
- [ ] Map database transactions
- [ ] Note caching layers
- [ ] Document event emissions
### Critical Paths
- [ ] List business-critical user flows
- [ ] Identify high-traffic endpoints
- [ ] Note payment/security-sensitive paths
- [ ] Document compliance-required flows
## Integration Points for {{system/module}}
| Integration | Type | Involved Components | Test Priority |
|-------------|------|---------------------|---------------|
| {{endpoint1}} | API | Controller → Service → DB | High |
| {{flow1}} | Service | ServiceA → ServiceB | Medium |
| {{external1}} | External | Service → PaymentAPI | High |
| {{event1}} | Message | Producer → Queue → Consumer | Medium |
## Test Scenarios
### API Integration Tests
1. {{endpoint}} - Happy path with valid auth
2. {{endpoint}} - Invalid input validation
3. {{endpoint}} - Unauthorized access
4. {{endpoint}} - Database persistence verification
### E2E User Flows
1. {{userFlow1}} - Complete journey
2. {{userFlow1}} - Interrupted journey (error recovery)
3. {{userFlow2}} - Edge case scenario
### External Integrations
1. {{externalService}} - Successful response
2. {{externalService}} - Timeout handling
3. {{externalService}} - Error response handling
/**
* Integration tests for {{Endpoint}} API
* Generated for existing code - retrofit coverage
*
* Tests: {{HTTP_METHOD}} {{path}}
*/
describe('{{Resource}} API Integration', () => {
let app: Application;
let db: TestDatabase;
let authToken: string;
beforeAll(async () => {
// Setup test environment
db = await createTestDatabase();
app = await createApp({ database: db });
authToken = await generateTestAuthToken({ role: 'user' });
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
// Clean state for each test
await db.clear();
await db.seed(baseFixtures);
});
describe('GET {{path}}', () => {
it('should return {{resource}} list with valid auth', async () => {
// Arrange
await db.seed({
{{table}}: [fixture1, fixture2, fixture3],
});
// Act
const response = await request(app)
.get('{{path}}')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
// Assert
expect(response.body).toMatchObject({
data: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
{{field}}: expect.any({{Type}}),
}),
]),
meta: {
total: 3,
page: 1,
},
});
});
it('should filter by {{filterField}}', async () => {
// Arrange
await db.seed({
{{table}}: [
{ ...fixture1, {{filterField}}: 'A' },
{ ...fixture2, {{filterField}}: 'B' },
],
});
// Act
const response = await request(app)
.get('{{path}}?{{filterField}}=A')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
// Assert
expect(response.body.data).toHaveLength(1);
expect(response.body.data[0].{{filterField}}).toBe('A');
});
it('should return 401 without authentication', async () => {
await request(app)
.get('{{path}}')
.expect(401);
});
it('should return 403 without required permission', async () => {
const limitedToken = await generateTestAuthToken({ role: 'guest' });
await request(app)
.get('{{path}}')
.set('Authorization', `Bearer ${limitedToken}`)
.expect(403);
});
});
describe('POST {{path}}', () => {
it('should create {{resource}} and persist to database', async () => {
// Arrange
const payload = {
{{field1}}: '{{value1}}',
{{field2}}: {{value2}},
};
// Act
const response = await request(app)
.post('{{path}}')
.set('Authorization', `Bearer ${authToken}`)
.send(payload)
.expect(201);
// Assert - Response
expect(response.body).toMatchObject({
id: expect.any(String),
...payload,
createdAt: expect.any(String),
});
// Assert - Database persistence
const dbRecord = await db.{{table}}.findById(response.body.id);
expect(dbRecord).toBeDefined();
expect(dbRecord.{{field1}}).toBe(payload.{{field1}});
});
it('should return 400 for invalid payload', async () => {
const invalidPayload = {
{{field1}}: '', // Invalid: empty required field
};
const response = await request(app)
.post('{{path}}')
.set('Authorization', `Bearer ${authToken}`)
.send(invalidPayload)
.expect(400);
expect(response.body.error).toMatchObject({
code: 'VALIDATION_ERROR',
details: expect.arrayContaining([
expect.objectContaining({
field: '{{field1}}',
}),
]),
});
});
it('should handle duplicate {{uniqueField}} conflict', async () => {
// Arrange
await db.{{table}}.create({ {{uniqueField}}: 'existing' });
// Act
const response = await request(app)
.post('{{path}}')
.set('Authorization', `Bearer ${authToken}`)
.send({ {{uniqueField}}: 'existing' })
.expect(409);
// Assert
expect(response.body.error.code).toBe('CONFLICT');
});
});
describe('PUT {{path}}/:id', () => {
it('should update existing {{resource}}', async () => {
// Arrange
const existing = await db.{{table}}.create(fixture1);
const updates = { {{field}}: '{{updatedValue}}' };
// Act
const response = await request(app)
.put(`{{path}}/${existing.id}`)
.set('Authorization', `Bearer ${authToken}`)
.send(updates)
.expect(200);
// Assert
expect(response.body.{{field}}).toBe('{{updatedValue}}');
// Verify database
const dbRecord = await db.{{table}}.findById(existing.id);
expect(dbRecord.{{field}}).toBe('{{updatedValue}}');
});
it('should return 404 for non-existent resource', async () => {
await request(app)
.put('{{path}}/non-existent-id')
.set('Authorization', `Bearer ${authToken}`)
.send({ {{field}}: 'value' })
.expect(404);
});
});
describe('DELETE {{path}}/:id', () => {
it('should delete {{resource}} and remove from database', async () => {
// Arrange
const existing = await db.{{table}}.create(fixture1);
// Act
await request(app)
.delete(`{{path}}/${existing.id}`)
.set('Authorization', `Bearer ${authToken}`)
.expect(204);
// Assert - Verify deletion
const dbRecord = await db.{{table}}.findById(existing.id);
expect(dbRecord).toBeNull();
});
});
});
/**
* Database integration tests for {{Entity}}Repository
* Generated for existing code - retrofit coverage
*/
describe('{{Entity}}Repository Integration', () => {
let repository: {{Entity}}Repository;
let db: TestDatabase;
beforeAll(async () => {
db = await createTestDatabase();
repository = new {{Entity}}Repository(db.connection);
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear('{{table}}');
});
describe('create', () => {
it('should persist entity with all fields', async () => {
// Arrange
const data: Create{{Entity}}Dto = {
{{field1}}: '{{value1}}',
{{field2}}: {{value2}},
};
// Act
const result = await repository.create(data);
// Assert
expect(result.id).toBeDefined();
expect(result.createdAt).toBeInstanceOf(Date);
// Verify raw SQL
const raw = await db.raw(
'SELECT * FROM {{table}} WHERE id = $1',
[result.id]
);
expect(raw.rows[0].{{field1}}).toBe(data.{{field1}});
});
it('should enforce unique constraint on {{uniqueField}}', async () => {
// Arrange
await repository.create({ {{uniqueField}}: 'duplicate' });
// Act & Assert
await expect(
repository.create({ {{uniqueField}}: 'duplicate' })
).rejects.toThrow(/unique constraint/i);
});
it('should enforce foreign key constraint', async () => {
// Arrange
const invalidForeignKey = 'non-existent-id';
// Act & Assert
await expect(
repository.create({ {{foreignKeyField}}: invalidForeignKey })
).rejects.toThrow(/foreign key/i);
});
});
describe('findById', () => {
it('should return entity with relations loaded', async () => {
// Arrange
const parent = await db.{{parentTable}}.create(parentFixture);
const entity = await repository.create({
...entityFixture,
{{foreignKeyField}}: parent.id,
});
// Act
const result = await repository.findById(entity.id, {
includeRelations: true,
});
// Assert
expect(result).toBeDefined();
expect(result.{{relation}}).toBeDefined();
expect(result.{{relation}}.id).toBe(parent.id);
});
});
describe('complex queries', () => {
beforeEach(async () => {
// Seed diverse test data
await repository.createMany([
{ status: 'active', category: 'A', createdAt: new Date('2025-01-01') },
{ status: 'active', category: 'B', createdAt: new Date('2025-01-10') },
{ status: 'inactive', category: 'A', createdAt: new Date('2025-01-05') },
]);
});
it('should filter with multiple conditions', async () => {
const results = await repository.findAll({
where: { status: 'active', category: 'A' },
});
expect(results).toHaveLength(1);
});
it('should paginate results correctly', async () => {
const page1 = await repository.findAll({ limit: 2, offset: 0 });
const page2 = await repository.findAll({ limit: 2, offset: 2 });
expect(page1).toHaveLength(2);
expect(page2).toHaveLength(1);
expect(page1[0].id).not.toBe(page2[0].id);
});
it('should order by specified field', async () => {
const results = await repository.findAll({
orderBy: 'createdAt',
order: 'DESC',
});
expect(new Date(results[0].createdAt).getTime())
.toBeGreaterThan(new Date(results[1].createdAt).getTime());
});
});
describe('transactions', () => {
it('should rollback on error', async () => {
// Arrange
const initialCount = await repository.count();
// Act
try {
await db.transaction(async (trx) => {
await repository.create(validData, { transaction: trx });
throw new Error('Simulated failure');
});
} catch (e) {
// Expected
}
// Assert - No new records
const finalCount = await repository.count();
expect(finalCount).toBe(initialCount);
});
});
});
/**
* E2E tests for {{UserFlow}}
* Generated for existing code - retrofit coverage
*
* Flow: {{flowDescription}}
*/
import { test, expect } from '@playwright/test';
test.describe('{{UserFlow}}', () => {
test.beforeEach(async ({ page }) => {
// Reset test state
await resetTestDatabase();
await seedTestData(testFixtures);
});
test('complete {{flowName}} - happy path', async ({ page }) => {
// Step 1: {{step1Description}}
await page.goto('{{startUrl}}');
await expect(page).toHaveTitle(/{{expectedTitle}}/);
// Step 2: {{step2Description}}
await page.fill('[data-testid="{{input1}}"]', '{{value1}}');
await page.fill('[data-testid="{{input2}}"]', '{{value2}}');
await page.click('[data-testid="{{submitButton}}"]');
// Step 3: {{step3Description}}
await expect(page).toHaveURL(/{{expectedUrlPattern}}/);
await expect(page.locator('[data-testid="{{successIndicator}}"]'))
.toBeVisible();
// Step 4: Verify final state
await expect(page.locator('[data-testid="{{resultElement}}"]'))
.toContainText('{{expectedText}}');
// Verify database state (optional)
const dbState = await verifyDatabaseState();
expect(dbState.{{field}}).toBe('{{expectedValue}}');
});
test('{{flowName}} - validation errors', async ({ page }) => {
await page.goto('{{startUrl}}');
// Submit with invalid data
await page.fill('[data-testid="{{input1}}"]', '{{invalidValue}}');
await page.click('[data-testid="{{submitButton}}"]');
// Verify error display
await expect(page.locator('[data-testid="error-{{input1}}"]'))
.toBeVisible();
await expect(page.locator('[data-testid="error-{{input1}}"]'))
.toContainText('{{expectedErrorMessage}}');
// Verify no navigation occurred
await expect(page).toHaveURL('{{startUrl}}');
});
test('{{flowName}} - authentication required', async ({ page }) => {
// Attempt to access protected route without auth
await page.goto('{{protectedUrl}}');
// Should redirect to login
await expect(page).toHaveURL(/\/login/);
});
test('{{flowName}} - handles server error gracefully', async ({ page }) => {
// Mock API to return error
await page.route('**/api/{{endpoint}}', (route) => {
route.fulfill({ status: 500, body: 'Internal Server Error' });
});
await page.goto('{{startUrl}}');
await page.click('[data-testid="{{triggerButton}}"]');
// Verify error handling UI
await expect(page.locator('[data-testid="error-message"]'))
.toBeVisible();
await expect(page.locator('[data-testid="retry-button"]'))
.toBeVisible();
});
test('{{flowName}} - mobile viewport', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('{{startUrl}}');
// Verify mobile-specific elements
await expect(page.locator('[data-testid="mobile-menu"]'))
.toBeVisible();
await expect(page.locator('[data-testid="desktop-nav"]'))
.toBeHidden();
// Complete flow on mobile
await page.click('[data-testid="mobile-menu"]');
await page.click('[data-testid="nav-{{link}}"]');
await expect(page).toHaveURL(/{{expectedUrl}}/);
});
});
test.describe('{{UserFlow}} - Cross-browser', () => {
test('works in Firefox', async ({ page, browserName }) => {
test.skip(browserName !== 'firefox', 'Firefox-specific test');
// Firefox-specific flow test
});
test('works in WebKit', async ({ page, browserName }) => {
test.skip(browserName !== 'webkit', 'WebKit-specific test');
// Safari/WebKit-specific flow test
});
});
/**
* Service integration tests for {{ServiceName}}
* Generated for existing code - retrofit coverage
*
* Tests service-to-service and service-to-database interactions
*/
describe('{{ServiceName}} Integration', () => {
let service: {{ServiceName}};
let db: TestDatabase;
let mockExternalApi: MockServer;
beforeAll(async () => {
db = await createTestDatabase();
mockExternalApi = await createMockServer(externalApiConfig);
service = new {{ServiceName}}({
repository: new {{Repository}}(db.connection),
externalClient: new ExternalClient({
baseUrl: mockExternalApi.url,
}),
});
});
afterAll(async () => {
await db.close();
await mockExternalApi.close();
});
beforeEach(async () => {
await db.clear();
mockExternalApi.reset();
});
describe('{{complexOperation}}', () => {
it('should coordinate database and external API', async () => {
// Arrange
mockExternalApi.on('POST', '/external/endpoint').respond({
status: 200,
body: { externalId: 'ext-123' },
});
const input = createTestInput();
// Act
const result = await service.{{complexOperation}}(input);
// Assert - Service response
expect(result).toMatchObject({
id: expect.any(String),
externalId: 'ext-123',
status: 'completed',
});
// Assert - Database state
const dbRecord = await db.{{table}}.findById(result.id);
expect(dbRecord.status).toBe('completed');
expect(dbRecord.externalId).toBe('ext-123');
// Assert - External API was called correctly
expect(mockExternalApi.calls('/external/endpoint')).toHaveLength(1);
expect(mockExternalApi.lastCall('/external/endpoint').body)
.toMatchObject({ {{expectedPayload}} });
});
it('should rollback database on external API failure', async () => {
// Arrange
mockExternalApi.on('POST', '/external/endpoint').respond({
status: 500,
body: { error: 'External service down' },
});
const initialCount = await db.{{table}}.count();
// Act & Assert
await expect(service.{{complexOperation}}(input))
.rejects.toThrow('External service');
// Verify rollback
const finalCount = await db.{{table}}.count();
expect(finalCount).toBe(initialCount);
});
it('should handle external API timeout', async () => {
// Arrange
mockExternalApi.on('POST', '/external/endpoint').delay(10000);
// Act & Assert
await expect(service.{{complexOperation}}(input))
.rejects.toThrow(/timeout/i);
});
it('should retry on transient failures', async () => {
// Arrange - Fail twice, then succeed
let callCount = 0;
mockExternalApi.on('POST', '/external/endpoint').respond(() => {
callCount++;
if (callCount < 3) {
return { status: 503 };
}
return { status: 200, body: { externalId: 'ext-123' } };
});
// Act
const result = await service.{{complexOperation}}(input);
// Assert
expect(result.externalId).toBe('ext-123');
expect(mockExternalApi.calls('/external/endpoint')).toHaveLength(3);
});
});
});
# docker-compose.test.yml
version: '3.8'
services:
test-db:
image: postgres:15
environment:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/data
test-redis:
image: redis:7
ports:
- "6380:6379"
// test/helpers/database.ts
export async function createTestDatabase(): Promise<TestDatabase> {
const workerId = process.env.JEST_WORKER_ID || '1';
const dbName = `test_${workerId}`;
const connection = await createConnection({
...baseConfig,
database: dbName,
});
await runMigrations(connection);
return {
connection,
clear: async (table?: string) => { /* ... */ },
seed: async (fixtures) => { /* ... */ },
raw: async (sql, params) => { /* ... */ },
close: async () => { /* ... */ },
};
}
When generating tests, provide:
## Generated Integration/E2E Tests for {{target}}
### Analysis Summary
- **Target:** {{system/module/flow}}
- **Test Type:** Integration / E2E / Both
- **Current Coverage:** {{percentage}}%
- **Test Cases Generated:** {{count}}
- **Expected Coverage After:** {{percentage}}%
### Files Created/Modified
- `{{testFilePath1}}`
- `{{testFilePath2}}`
### Test Summary
| Category | Count |
|----------|-------|
| API integration | {{n}} |
| Database integration | {{n}} |
| E2E user flows | {{n}} |
| External services | {{n}} |
| Error scenarios | {{n}} |
### Dependencies Required
- Test database (PostgreSQL/MySQL)
- Mock server for external APIs
- {{other dependencies}}
### Run Tests
\`\`\`bash
# Integration tests
npx jest {{integrationTestPath}} --runInBand
# E2E tests
npx playwright test {{e2eTestPath}}
\`\`\`
| Agent | Usage |
|---|---|
| QA Engineer | Primary: Generates integration/E2E tests for existing systems, reviews test coverage |
| Senior Developer | Reviews integration tests, advises on test boundaries |
| DevOps Engineer | Sets up test infrastructure, CI/CD integration |
| Anti-Pattern | Problem | Better Approach |
|---|---|---|
| Shared mutable state | Flaky tests | Fresh state per test |
| Testing in production DB | Data corruption risk | Isolated test database |
| Over-mocking | Tests don't reflect reality | Use real components |
| Slow tests accepted | Tests get skipped | Optimize or parallelize |
| No cleanup | Resource leaks | Always teardown |
| Hardcoded test data | Brittle tests | Use fixtures/factories |
| E2E for everything | Slow feedback loop | Use test pyramid |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.