Testing Framework Implementation Guide
Status: Complete Implementation Guide
Version: 1.0
Purpose: Step-by-step procedures for implementing comprehensive testing frameworks
Applicable To: Any modern web application development
Overview
This guide provides comprehensive procedures for implementing a robust testing framework following the testing pyramid model. The approach balances speed with thoroughness, emphasizing fast unit tests while maintaining critical end-to-end coverage.
Key Benefits
- Fast Feedback: 70% unit tests provide immediate validation
- Reliable Quality: 90% automated test coverage ensures consistency
- Cost Effective: Early detection prevents expensive bugs
- Continuous Improvement: Data-driven metrics guide optimization
Testing Strategy Setup
Step 1: Testing Pyramid Configuration
Implement the proven testing pyramid model:
graph TD
A[Testing Pyramid] --> B[Unit Tests 70%]
A --> C[Integration Tests 20%]
A --> D[E2E Tests 10%]
B --> E[Fast Execution < 5s]
B --> F[High Coverage 90%]
B --> G[Isolated Dependencies]
C --> H[API Contract Tests]
C --> I[Database Integration]
C --> J[Service Communication]
D --> K[User Journey Tests]
D --> L[Critical Path Validation]
D --> M[Cross-browser Testing]
Step 2: Coverage Targets
Set realistic but comprehensive coverage goals:
// jest.config.js - Coverage configuration
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/serviceWorker.ts'
],
coverageThreshold: {
global: {
branches: 85,
functions: 85,
lines: 85,
statements: 85
},
// Critical paths require 100% coverage
'./src/lib/validation/': {
branches: 100,
functions: 100,
lines: 100,
statements: 100
},
'./src/api/': {
branches: 95,
functions: 95,
lines: 95,
statements: 95
}
}
};
Unit Testing Setup
Step 1: Jest Configuration
// jest.config.js - Complete Jest setup
module.exports = {
// Test environment
testEnvironment: 'jsdom',
// Setup files
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
// Module mapping
moduleNameMapping: {
'^@/(.*)CODE_BLOCK_1#39;: '<rootDir>/src/$1',
'\\.(css|less|scss|sass)CODE_BLOCK_1#39;: 'identity-obj-proxy'
},
// Transform configuration
transform: {
'^.+\\.(js|jsx|ts|tsx)CODE_BLOCK_1#39;: 'babel-jest'
},
// Test file patterns
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}'
],
// Coverage configuration
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
// Performance
maxWorkers: '50%',
cache: true,
// Mock configuration
clearMocks: true,
restoreMocks: true
};
Step 2: Testing Utilities Setup
// src/setupTests.ts - Test environment setup
import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';
import 'jest-canvas-mock';
// Configure testing library
configure({ testIdAttribute: 'data-testid' });
// Mock global objects
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}));
// Mock fetch
global.fetch = jest.fn();
// Setup fake timers
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.clearAllMocks();
});
Step 3: Unit Test Implementation Patterns
// src/lib/validation/__tests__/EmailValidator.test.ts
import { EmailValidator } from '../EmailValidator';
describe('EmailValidator', () => {
describe('validate', () => {
it('should accept valid email formats', () => {
// Arrange
const validEmails = [
'user@example.com',
'user+tag@example.com',
'user.name@example.co.uk',
'test123@subdomain.example.org'
];
// Act & Assert
validEmails.forEach(email => {
expect(EmailValidator.validate(email)).toBe(true);
});
});
it('should reject invalid email formats', () => {
// Arrange
const invalidEmails = [
'notanemail',
'@example.com',
'user@',
'user..name@example.com',
'user@.com',
'user@com'
];
// Act & Assert
invalidEmails.forEach(email => {
expect(EmailValidator.validate(email)).toBe(false);
});
});
it('should handle edge cases gracefully', () => {
// Test null, undefined, empty string
expect(EmailValidator.validate(null)).toBe(false);
expect(EmailValidator.validate(undefined)).toBe(false);
expect(EmailValidator.validate('')).toBe(false);
expect(EmailValidator.validate(' ')).toBe(false);
});
it('should handle very long emails', () => {
const longEmail = 'a'.repeat(254) + '@example.com';
expect(EmailValidator.validate(longEmail)).toBe(false);
});
});
});
βοΈ Component Testing Setup
Step 1: React Testing Library Configuration
// src/test-utils/render.tsx - Custom render utility
import React, { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from 'styled-components';
import { theme } from '../styles/theme';
// Test wrapper component
const AllTheProviders: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
cacheTime: 0,
},
},
});
return (
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
</QueryClientProvider>
</BrowserRouter>
);
};
// Custom render function
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllTheProviders, ...options });
export * from '@testing-library/react';
export { customRender as render };
Step 2: Component Test Implementation
// src/components/EmailEditor/__tests__/EmailEditor.test.tsx
import { render, screen, fireEvent, waitFor } from '../../../test-utils/render';
import { EmailEditor } from '../EmailEditor';
describe('EmailEditor', () => {
const defaultProps = {
onSave: jest.fn(),
onCancel: jest.fn(),
initialContent: '',
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should render email editor interface', () => {
render(<EmailEditor {...defaultProps} />);
expect(screen.getByRole('textbox', { name: /email content/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
});
it('should allow drag and drop of content blocks', async () => {
render(<EmailEditor {...defaultProps} />);
const textBlock = screen.getByText('Text Block');
const canvas = screen.getByTestId('editor-canvas');
// Simulate drag and drop
fireEvent.dragStart(textBlock);
fireEvent.dragOver(canvas);
fireEvent.drop(canvas);
await waitFor(() => {
expect(screen.getByRole('textbox')).toBeInTheDocument();
expect(screen.getByText('Click to edit text')).toBeInTheDocument();
});
});
it('should validate content before saving', async () => {
const onSave = jest.fn();
render(<EmailEditor {...defaultProps} onSave={onSave} />);
const saveButton = screen.getByRole('button', { name: /save/i });
// Try to save without content
fireEvent.click(saveButton);
await waitFor(() => {
expect(screen.getByText('Content is required')).toBeInTheDocument();
expect(onSave).not.toHaveBeenCalled();
});
});
it('should call onSave with correct data when valid', async () => {
const onSave = jest.fn();
render(<EmailEditor {...defaultProps} onSave={onSave} />);
const editor = screen.getByRole('textbox', { name: /email content/i });
const saveButton = screen.getByRole('button', { name: /save/i });
// Add content
fireEvent.change(editor, { target: { value: 'Test email content' } });
// Save
fireEvent.click(saveButton);
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith({
content: 'Test email content',
timestamp: expect.any(Number)
});
});
});
});
Integration Testing Setup
Step 1: API Testing Configuration
// src/test-utils/api-test-setup.ts
import request from 'supertest';
import { app } from '../app';
import { setupTestDatabase, clearTestDatabase } from './test-database';
export class APITestHelper {
static async setupTest() {
await setupTestDatabase();
}
static async teardownTest() {
await clearTestDatabase();
}
static request() {
return request(app);
}
static async createTestUser(userData = {}) {
const defaultUser = {
email: 'test@example.com',
password: 'password123',
name: 'Test User'
};
const response = await this.request()
.post('/api/auth/register')
.send({ ...defaultUser, ...userData });
return response.body;
}
static async authenticateUser(email = 'test@example.com', password = 'password123') {
const response = await this.request()
.post('/api/auth/login')
.send({ email, password });
return response.body.token;
}
}
Step 2: Integration Test Implementation
// src/api/__tests__/campaigns.integration.test.ts
import { APITestHelper } from '../../test-utils/api-test-setup';
describe('Campaigns API Integration', () => {
let authToken: string;
let testUserId: string;
beforeAll(async () => {
await APITestHelper.setupTest();
});
afterAll(async () => {
await APITestHelper.teardownTest();
});
beforeEach(async () => {
const user = await APITestHelper.createTestUser();
testUserId = user.id;
authToken = await APITestHelper.authenticateUser();
});
describe('POST /api/campaigns', () => {
it('should create a new campaign with valid data', async () => {
const campaignData = {
name: 'Test Campaign',
subject: 'Test Subject',
content: '<p>Test content</p>',
recipients: ['test@example.com']
};
const response = await APITestHelper.request()
.post('/api/campaigns')
.set('Authorization', `Bearer ${authToken}`)
.send(campaignData)
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'Test Campaign',
subject: 'Test Subject',
userId: testUserId,
status: 'draft'
});
expect(response.body.createdAt).toBeDefined();
expect(response.body.updatedAt).toBeDefined();
});
it('should validate required fields', async () => {
const response = await APITestHelper.request()
.post('/api/campaigns')
.set('Authorization', `Bearer ${authToken}`)
.send({})
.expect(400);
expect(response.body.errors).toContain('Name is required');
expect(response.body.errors).toContain('Subject is required');
});
it('should require authentication', async () => {
await APITestHelper.request()
.post('/api/campaigns')
.send({ name: 'Test', subject: 'Test' })
.expect(401);
});
});
describe('GET /api/campaigns', () => {
it('should return user campaigns with pagination', async () => {
// Create test campaigns
await Promise.all([
APITestHelper.request()
.post('/api/campaigns')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Campaign 1', subject: 'Subject 1' }),
APITestHelper.request()
.post('/api/campaigns')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Campaign 2', subject: 'Subject 2' })
]);
const response = await APITestHelper.request()
.get('/api/campaigns')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.campaigns).toHaveLength(2);
expect(response.body.pagination).toMatchObject({
total: 2,
page: 1,
limit: 10
});
});
});
});
End-to-End Testing Setup
Step 1: Cypress Configuration
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
video: true,
screenshotOnRunFailure: true,
// Timeouts
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000,
// Test file patterns
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
// Environment
env: {
apiUrl: 'http://localhost:3001/api',
testUser: {
email: 'test@example.com',
password: 'password123'
}
},
setupNodeEvents(on, config) {
// Task definitions
on('task', {
resetDatabase() {
// Reset test database
return null;
},
seedTestData() {
// Seed test data
return null;
}
});
}
}
});
Step 2: E2E Test Implementation
// cypress/e2e/campaign-creation.cy.ts
describe('Campaign Creation Flow', () => {
beforeEach(() => {
// Reset database and seed data
cy.task('resetDatabase');
cy.task('seedTestData');
// Login
cy.visit('/login');
cy.get('[data-testid="email-input"]').type(Cypress.env('testUser').email);
cy.get('[data-testid="password-input"]').type(Cypress.env('testUser').password);
cy.get('[data-testid="login-button"]').click();
// Wait for dashboard
cy.url().should('include', '/dashboard');
});
it('should create a new email campaign successfully', () => {
// Navigate to campaign creation
cy.get('[data-testid="create-campaign-button"]').click();
cy.url().should('include', '/campaigns/new');
// Fill campaign details
cy.get('[data-testid="campaign-name-input"]').type('Welcome Series Campaign');
cy.get('[data-testid="campaign-subject-input"]').type('Welcome to NudgeCampaign!');
// Add email content
cy.get('[data-testid="email-editor"]').click();
cy.get('[data-testid="text-block"]').drag('[data-testid="editor-canvas"]');
cy.get('[data-testid="text-editor"]').type('Welcome to our platform!');
// Add recipients
cy.get('[data-testid="recipients-tab"]').click();
cy.get('[data-testid="add-recipient-button"]').click();
cy.get('[data-testid="recipient-email-input"]').type('test@example.com');
cy.get('[data-testid="add-recipient-confirm"]').click();
// Save campaign
cy.get('[data-testid="save-campaign-button"]').click();
// Verify success
cy.get('[data-testid="success-message"]').should('contain', 'Campaign created successfully');
cy.url().should('match', /\/campaigns\/[a-zA-Z0-9-]+$/);
// Verify campaign appears in list
cy.visit('/campaigns');
cy.get('[data-testid="campaign-list"]').should('contain', 'Welcome Series Campaign');
});
it('should validate required fields', () => {
cy.get('[data-testid="create-campaign-button"]').click();
// Try to save without filling required fields
cy.get('[data-testid="save-campaign-button"]').click();
// Check validation messages
cy.get('[data-testid="name-error"]').should('contain', 'Campaign name is required');
cy.get('[data-testid="subject-error"]').should('contain', 'Subject line is required');
});
it('should allow campaign preview', () => {
// Create and configure campaign
cy.get('[data-testid="create-campaign-button"]').click();
cy.get('[data-testid="campaign-name-input"]').type('Preview Test');
cy.get('[data-testid="campaign-subject-input"]').type('Preview Subject');
// Add content
cy.get('[data-testid="email-editor"]').click();
cy.get('[data-testid="text-block"]').drag('[data-testid="editor-canvas"]');
cy.get('[data-testid="text-editor"]').type('Preview content');
// Open preview
cy.get('[data-testid="preview-button"]').click();
// Verify preview modal
cy.get('[data-testid="preview-modal"]').should('be.visible');
cy.get('[data-testid="preview-subject"]').should('contain', 'Preview Subject');
cy.get('[data-testid="preview-content"]').should('contain', 'Preview content');
// Close preview
cy.get('[data-testid="close-preview"]').click();
cy.get('[data-testid="preview-modal"]').should('not.exist');
});
});
Test Monitoring and Metrics
Step 1: Test Reporting Setup
// jest.config.js - Add reporters
module.exports = {
// ... other config
reporters: [
'default',
['jest-junit', {
outputDirectory: 'test-results',
outputName: 'junit.xml'
}],
['jest-html-reporter', {
pageTitle: 'Test Report',
outputPath: 'test-results/test-report.html'
}]
]
};
Step 2: CI/CD Integration
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit -- --coverage --watchAll=false
- name: Run integration tests
run: npm run test:integration
- name: Run E2E tests
run: npm run test:e2e:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: test-results/
Testing Implementation Checklist
Foundation Setup
- Jest configuration with coverage thresholds
- Testing utilities and test helpers
- Mock strategies for external dependencies
- Test database setup and teardown
- CI/CD pipeline integration
Unit Testing
- Component testing with React Testing Library
- Utility function testing
- Validation logic testing
- Error handling testing
- Edge case coverage
Integration Testing
- API endpoint testing
- Database integration testing
- Service communication testing
- Authentication flow testing
- Error scenario testing
End-to-End Testing
- Critical user journey testing
- Cross-browser compatibility
- Mobile responsiveness
- Performance benchmarking
- Accessibility validation
Monitoring & Reporting
- Test metrics collection
- Coverage reporting
- Performance tracking
- Failure analysis
- Continuous improvement process
This guide provides a comprehensive framework for implementing robust testing across all layers of your application. Customize the specific tools and configurations based on your technology stack and requirements.