Test Patterns
npx machina-cli add skill rohitg00/skillkit/test-patterns --openclawTest Patterns
You are applying proven testing patterns to write maintainable, reliable tests. These patterns help ensure tests are readable, focused, and trustworthy.
Core Pattern: Arrange-Act-Assert (AAA)
Structure every test with three distinct phases:
// Arrange - Set up test data and dependencies
const user = createTestUser({ role: 'admin' });
const service = new UserService(mockRepository);
// Act - Execute the code under test
const result = await service.updateRole(user.id, 'member');
// Assert - Verify the expected outcome
expect(result.role).toBe('member');
expect(mockRepository.save).toHaveBeenCalledWith(user);
Guidelines:
- Keep sections visually separated (blank lines or comments)
- Arrange should be minimal - only what's needed for this test
- Act should be a single operation
- Assert should verify one logical concept
Pattern: Given-When-Then (BDD Style)
For behavior-focused tests:
describe('Shopping Cart', () => {
describe('when adding an item', () => {
it('should increase the item count', () => {
// Given
const cart = new Cart();
// When
cart.add({ id: '1', quantity: 2 });
// Then
expect(cart.itemCount).toBe(2);
});
});
});
Pattern: Test Data Builders
Create flexible test data without repetition:
// Builder function
function createTestOrder(overrides = {}) {
return {
id: 'order-123',
status: 'pending',
items: [],
total: 0,
...overrides
};
}
// Usage
const completedOrder = createTestOrder({ status: 'completed', total: 99.99 });
const emptyOrder = createTestOrder({ items: [] });
Benefits:
- Reduces test setup boilerplate
- Makes test intent clearer
- Easy to create variations
Pattern: Object Mother
Factory for complex test objects:
class TestUserFactory {
static admin() {
return new User({ role: 'admin', permissions: ALL_PERMISSIONS });
}
static guest() {
return new User({ role: 'guest', permissions: [] });
}
static withSubscription(tier) {
return new User({ subscription: { tier, active: true } });
}
}
Pattern: Parameterized Tests
Test multiple cases efficiently:
describe('isValidEmail', () => {
const validCases = [
'user@example.com',
'user.name@domain.co.uk',
'user+tag@example.org'
];
const invalidCases = [
'',
'not-an-email',
'@no-local.com',
'no-domain@'
];
test.each(validCases)('should accept valid email: %s', (email) => {
expect(isValidEmail(email)).toBe(true);
});
test.each(invalidCases)('should reject invalid email: %s', (email) => {
expect(isValidEmail(email)).toBe(false);
});
});
Pattern: Test Fixtures
Reusable test setup:
describe('OrderService', () => {
let service;
let mockPaymentGateway;
let mockInventory;
beforeEach(() => {
mockPaymentGateway = createMockPaymentGateway();
mockInventory = createMockInventory();
service = new OrderService(mockPaymentGateway, mockInventory);
});
afterEach(() => {
jest.clearAllMocks();
});
});
Pattern: Spy on Dependencies
Verify interactions without implementation:
it('should send notification on order completion', async () => {
const notifySpy = jest.spyOn(notificationService, 'send');
await orderService.complete(orderId);
expect(notifySpy).toHaveBeenCalledWith({
type: 'order_completed',
orderId: orderId
});
});
Pattern: Test Doubles
Choose the right type:
| Type | Purpose | When to Use |
|---|---|---|
| Stub | Returns canned data | Need predictable inputs |
| Mock | Verifies interactions | Testing side effects |
| Spy | Records calls | Partial mocking |
| Fake | Working implementation | Need realistic behavior |
Pattern: Test Isolation
Ensure tests don't affect each other:
- Fresh instances - Create new objects in each test
- Reset mocks - Clear mock state between tests
- Clean up - Remove side effects (files, database rows)
- No shared mutable state - Avoid global variables
Naming Conventions
Test names should describe:
- What is being tested
- Under what conditions
- What the expected outcome is
Good examples:
shouldReturnEmptyArrayWhenNoItemsExistthrowsErrorWhenUserNotAuthenticatedcalculatesDiscountForPremiumMembers
Test Organization
src/
services/
UserService.ts
UserService.test.ts # Co-located tests
tests/
integration/
api.test.ts # Integration tests
e2e/
checkout.spec.ts # End-to-end tests
Verification Checklist
For each test:
- Single responsibility (tests one thing)
- Clear AAA or GWT structure
- Descriptive name
- Fast execution (< 100ms for unit tests)
- Deterministic (no flakiness)
- Independent (runs in any order)
Source
git clone https://github.com/rohitg00/skillkit/blob/main/packages/core/src/methodology/packs/testing/test-patterns/SKILL.mdView on GitHub Overview
Test Patterns offers a collection of proven patterns to write maintainable, reliable tests. It covers structuring tests with AAA, Given-When-Then, and data builders, plus test doubles and fixtures to reduce boilerplate.
How This Skill Works
The guide describes common testing patterns such as Arrange-Act-Assert and BDD-style Given-When-Then, illustrated with concrete code snippets. It also explains how to use Test Data Builders, Object Mother, parameterized tests, fixtures, and spies to simplify test setup and verify interactions.
When to Use It
- When you want clear, three-phase tests (Arrange-Act-Assert) for readability
- When you model behavior with Given-When-Then style in descriptive blocks
- When you need flexible test data without repetition via builders
- When you build complex test objects consistently with Object Mother
- When you want to run multiple cases efficiently with parameterized tests and reusable fixtures
Quick Start
- Step 1: Choose a pattern (AAA, Given-When-Then, or Builder) for the test
- Step 2: Refactor the test into setup (Arrange), action (Act), and assertion (Assert) blocks or equivalents
- Step 3: Introduce builders, fixtures, or spies to reduce boilerplate and verify interactions
Best Practices
- Keep Arrange minimal—only what's needed for the test
- Make Act a single operation to isolate behavior
- Assert to verify one logical concept at a time
- Use Test Data Builders and Object Mother to reduce boilerplate
- Leverage fixtures and spies to validate interactions and side effects
Example Use Cases
- AAA example: Admin updates a user's role with a mock repository
- BDD: Cart test using Given-When-Then to describe adding items
- Test Data Builder: createTestOrder with overrides for status or totals
- Object Mother: TestUserFactory.admin() and .guest() to model users
- Parameterized tests: isValidEmail across valid and invalid cases using test.each