From PACT
Provides test pyramid guidance, coverage targets, and patterns for unit, integration, E2E, performance, and security tests in PACT Test phase. Useful for designing test suites and prioritizing coverage.
How this skill is triggered — by the user, by Claude, or both
Slash command
/PACT:pact-testing-strategiesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Testing guidance for the Test phase of PACT. This skill provides frameworks
Testing guidance for the Test phase of PACT. This skill provides frameworks for designing comprehensive test suites and links to detailed testing patterns.
The test pyramid guides the distribution of test types for optimal coverage and speed.
/\
/ \ E2E Tests (Few)
/ \ - Critical user journeys
/ E2E \ - Slow, expensive
/--------\
/ \ Integration Tests (Some)
/Integration \ - API contracts
/--------------\ - Service interactions
/ \
/ Unit Tests \ Unit Tests (Many)
/ \ - Fast, isolated
/______________________\ - Business logic
| Layer | Target | Focus | Speed |
|---|---|---|---|
| Unit | 80%+ line coverage | Business logic, edge cases | <1s per test |
| Integration | Key paths covered | API contracts, data flow | <10s per test |
| E2E | Critical flows only | User journeys, happy paths | <60s per test |
describe('OrderService', () => {
describe('calculateTotal', () => {
it('should apply discount for orders over $100', () => {
// Arrange
const orderService = new OrderService();
const items = [
{ price: 50, quantity: 2 },
{ price: 20, quantity: 1 }
];
// Act
const total = orderService.calculateTotal(items);
// Assert
expect(total).toBe(108); // $120 - 10% discount
});
});
});
// BAD: Testing implementation details
it('should call repository.save once', () => {
await userService.createUser(userData);
expect(userRepository.save).toHaveBeenCalledTimes(1);
});
// GOOD: Testing behavior
it('should create a user with hashed password', async () => {
const user = await userService.createUser({
email: '[email protected]',
password: 'plaintext'
});
expect(user.email).toBe('[email protected]');
expect(user.password).not.toBe('plaintext');
expect(await bcrypt.compare('plaintext', user.password)).toBe(true);
});
// Mock setup
const mockEmailService = {
send: jest.fn().mockResolvedValue({ id: 'msg_123' })
};
const mockUserRepository = {
findByEmail: jest.fn(),
save: jest.fn().mockImplementation(user => ({ ...user, id: 'user_123' }))
};
describe('UserService', () => {
let userService;
beforeEach(() => {
jest.clearAllMocks();
userService = new UserService(mockUserRepository, mockEmailService);
});
it('should send welcome email after creating user', async () => {
mockUserRepository.findByEmail.mockResolvedValue(null);
await userService.createUser({ email: '[email protected]', name: 'New User' });
expect(mockEmailService.send).toHaveBeenCalledWith({
to: '[email protected]',
template: 'welcome',
data: expect.objectContaining({ name: 'New User' })
});
});
});
describe('validateEmail', () => {
// Happy path
it('should accept valid email', () => {
expect(validateEmail('[email protected]')).toBe(true);
});
// Edge cases
it.each([
['email with subdomain', '[email protected]'],
['email with plus sign', '[email protected]'],
['email with numbers', '[email protected]'],
])('should accept %s', (_, email) => {
expect(validateEmail(email)).toBe(true);
});
// Invalid cases
it.each([
['empty string', ''],
['missing @', 'userexample.com'],
['missing domain', 'user@'],
['spaces', 'user @example.com'],
['double @', 'user@@example.com'],
])('should reject %s', (_, email) => {
expect(validateEmail(email)).toBe(false);
});
// Boundary cases
it('should handle very long emails', () => {
const longEmail = 'a'.repeat(64) + '@' + 'b'.repeat(63) + '.com';
expect(validateEmail(longEmail)).toBe(true);
});
it('should reject emails exceeding max length', () => {
const tooLongEmail = 'a'.repeat(65) + '@' + 'b'.repeat(64) + '.com';
expect(validateEmail(tooLongEmail)).toBe(false);
});
});
describe('POST /api/users', () => {
let app;
let db;
beforeAll(async () => {
db = await setupTestDatabase();
app = createApp(db);
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
it('should create a user and return 201', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: '[email protected]',
name: 'New User',
password: 'securepassword123'
})
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
email: '[email protected]',
name: 'New User',
createdAt: expect.any(String)
});
// Verify password not returned
expect(response.body.password).toBeUndefined();
});
it('should return 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
name: 'Test User',
password: 'password123'
})
.expect(400);
expect(response.body).toMatchObject({
error: {
code: 'VALIDATION_ERROR',
message: expect.any(String)
}
});
});
it('should return 409 for duplicate email', async () => {
// Create first user
await request(app)
.post('/api/users')
.send({ email: '[email protected]', name: 'First', password: 'pass123' });
// Try to create duplicate
const response = await request(app)
.post('/api/users')
.send({ email: '[email protected]', name: 'Second', password: 'pass456' })
.expect(409);
expect(response.body.error.code).toBe('DUPLICATE_EMAIL');
});
});
describe('UserRepository', () => {
let db;
let userRepo;
beforeAll(async () => {
// Use test database (Docker or in-memory)
db = await setupTestDatabase();
await db.migrate();
userRepo = new UserRepository(db);
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
it('should persist and retrieve user', async () => {
const userData = {
email: '[email protected]',
name: 'Test User',
passwordHash: 'hashed'
};
const created = await userRepo.save(userData);
const retrieved = await userRepo.findById(created.id);
expect(retrieved).toMatchObject({
id: created.id,
email: '[email protected]',
name: 'Test User'
});
});
it('should return null for non-existent user', async () => {
const user = await userRepo.findById('non-existent-id');
expect(user).toBeNull();
});
it('should enforce unique email constraint', async () => {
await userRepo.save({ email: '[email protected]', name: 'First' });
await expect(
userRepo.save({ email: '[email protected]', name: 'Second' })
).rejects.toThrow('duplicate');
});
});
For detailed integration patterns: See references/integration-patterns.md
// Using Playwright
describe('Checkout Flow', () => {
let page;
beforeAll(async () => {
// Set up authenticated user
await seedTestData();
});
beforeEach(async () => {
page = await browser.newPage();
await page.goto('/login');
await loginAsTestUser(page);
});
afterEach(async () => {
await page.close();
});
it('should complete purchase successfully', async () => {
// Add item to cart
await page.goto('/products/test-product');
await page.click('[data-testid="add-to-cart"]');
// Go to cart
await page.click('[data-testid="cart-icon"]');
await expect(page.locator('[data-testid="cart-item"]')).toBeVisible();
// Proceed to checkout
await page.click('[data-testid="checkout-button"]');
// Fill shipping info
await page.fill('[data-testid="address"]', '123 Test St');
await page.fill('[data-testid="city"]', 'Test City');
await page.fill('[data-testid="zip"]', '12345');
await page.click('[data-testid="continue-to-payment"]');
// Complete payment (test card)
await page.fill('[data-testid="card-number"]', '4242424242424242');
await page.fill('[data-testid="expiry"]', '12/28');
await page.fill('[data-testid="cvc"]', '123');
await page.click('[data-testid="place-order"]');
// Verify confirmation
await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
await expect(page.locator('[data-testid="order-number"]')).toContainText(/ORD-/);
});
});
tests/
├── unit/ # Fast, isolated tests
│ ├── services/
│ │ ├── UserService.test.js
│ │ └── OrderService.test.js
│ ├── utils/
│ │ └── validation.test.js
│ └── models/
│ └── Order.test.js
│
├── integration/ # API and database tests
│ ├── api/
│ │ ├── users.test.js
│ │ └── orders.test.js
│ └── repositories/
│ └── UserRepository.test.js
│
├── e2e/ # End-to-end tests
│ ├── checkout.spec.js
│ ├── authentication.spec.js
│ └── user-profile.spec.js
│
├── fixtures/ # Shared test data
│ ├── users.js
│ └── orders.js
│
├── helpers/ # Shared test utilities
│ ├── setup.js
│ ├── factories.js
│ └── matchers.js
│
└── mocks/ # Shared mocks
├── emailService.js
└── paymentGateway.js
// Format: should [expected behavior] when [condition]
it('should return 404 when user does not exist', () => {});
it('should apply 10% discount when order total exceeds $100', () => {});
it('should send confirmation email when order is placed', () => {});
it('should throw ValidationError when email is invalid', () => {});
Read CODE phase decision logs at docs/decision-logs/{feature}-{domain}.md for:
Before completing TEST phase:
For comprehensive testing guidance:
Test Pyramid: references/test-pyramid.md
Integration Patterns: references/integration-patterns.md
Performance Testing: references/performance-testing.md
npx claudepluginhub synaptic-labs-ai/pact-plugin --plugin PACTProvides testing pyramid, unit patterns (AAA, isolation, parameterized, edge cases), and React Testing Library for component tests. Use when writing tests or setting up testing infrastructure.
Designs and implements testing strategies—unit, integration, E2E—for any codebase. Provides framework recommendations (Vitest, Playwright, pytest, etc.) and test structure templates.
Use when writing tests, designing test strategy, or reviewing test coverage. Covers test pyramid, naming, mocking, and flaky test policy.