javascript
examples
examples.js⚡javascript
/**
* 20.2 Integration & E2E Testing - Examples
*
* Demonstrates integration testing patterns
* and end-to-end testing concepts
*/
// ============================================
// PART 1: Simple Integration Tests
// ============================================
/**
* Example application layers to test together
*/
// Data Access Layer
class UserRepository {
constructor(database) {
this.db = database;
}
async findById(id) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
async findByEmail(email) {
return this.db.query('SELECT * FROM users WHERE email = ?', [email]);
}
async create(userData) {
const result = await this.db.query(
'INSERT INTO users (name, email, password) VALUES (?, ?, ?)',
[userData.name, userData.email, userData.password]
);
return { id: result.insertId, ...userData };
}
async update(id, userData) {
await this.db.query('UPDATE users SET name = ?, email = ? WHERE id = ?', [
userData.name,
userData.email,
id,
]);
return this.findById(id);
}
async delete(id) {
await this.db.query('DELETE FROM users WHERE id = ?', [id]);
return true;
}
}
// Service Layer
class UserService {
constructor(userRepository, emailService, validator) {
this.userRepo = userRepository;
this.emailService = emailService;
this.validator = validator;
}
async registerUser(userData) {
// Validate
const validation = this.validator.validate(userData);
if (!validation.valid) {
throw new Error(validation.errors.join(', '));
}
// Check if email exists
const existing = await this.userRepo.findByEmail(userData.email);
if (existing) {
throw new Error('Email already registered');
}
// Hash password (simplified)
const hashedPassword = this.hashPassword(userData.password);
// Create user
const user = await this.userRepo.create({
...userData,
password: hashedPassword,
});
// Send welcome email
await this.emailService.send(
user.email,
'Welcome!',
`Hello ${user.name}, welcome to our platform!`
);
return { id: user.id, name: user.name, email: user.email };
}
hashPassword(password) {
// Simplified - use bcrypt in real apps
return Buffer.from(password).toString('base64');
}
async getUserProfile(userId) {
const user = await this.userRepo.findById(userId);
if (!user) {
throw new Error('User not found');
}
return {
id: user.id,
name: user.name,
email: user.email,
};
}
}
// ============================================
// PART 2: In-Memory Database for Integration Tests
// ============================================
class InMemoryDatabase {
constructor() {
this.tables = new Map();
this.autoIncrement = new Map();
}
createTable(name, columns) {
this.tables.set(name, []);
this.autoIncrement.set(name, 1);
}
async query(sql, params = []) {
// Simple SQL parser for testing
const operation = sql.trim().split(' ')[0].toUpperCase();
switch (operation) {
case 'SELECT':
return this.select(sql, params);
case 'INSERT':
return this.insert(sql, params);
case 'UPDATE':
return this.update(sql, params);
case 'DELETE':
return this.delete(sql, params);
default:
throw new Error(`Unsupported operation: ${operation}`);
}
}
select(sql, params) {
const tableMatch = sql.match(/FROM\s+(\w+)/i);
const table = tableMatch ? tableMatch[1] : null;
const data = this.tables.get(table) || [];
const whereMatch = sql.match(/WHERE\s+(\w+)\s*=\s*\?/i);
if (whereMatch) {
const field = whereMatch[1];
const value = params[0];
return data.find((row) => row[field] == value) || null;
}
return data;
}
insert(sql, params) {
const tableMatch = sql.match(/INTO\s+(\w+)/i);
const table = tableMatch ? tableMatch[1] : null;
const data = this.tables.get(table);
const columnsMatch = sql.match(/\(([^)]+)\)\s+VALUES/i);
const columns = columnsMatch
? columnsMatch[1].split(',').map((c) => c.trim())
: [];
const id = this.autoIncrement.get(table);
this.autoIncrement.set(table, id + 1);
const row = { id };
columns.forEach((col, i) => {
row[col] = params[i];
});
data.push(row);
return { insertId: id };
}
update(sql, params) {
const tableMatch = sql.match(/UPDATE\s+(\w+)/i);
const table = tableMatch ? tableMatch[1] : null;
const data = this.tables.get(table);
const id = params[params.length - 1];
const row = data.find((r) => r.id == id);
if (row) {
const setMatch = sql.match(/SET\s+(.+?)\s+WHERE/i);
if (setMatch) {
const assignments = setMatch[1].split(',');
let paramIndex = 0;
assignments.forEach((a) => {
const col = a.split('=')[0].trim();
row[col] = params[paramIndex++];
});
}
}
return { affectedRows: row ? 1 : 0 };
}
delete(sql, params) {
const tableMatch = sql.match(/FROM\s+(\w+)/i);
const table = tableMatch ? tableMatch[1] : null;
const data = this.tables.get(table);
const id = params[0];
const index = data.findIndex((r) => r.id == id);
if (index !== -1) {
data.splice(index, 1);
}
return { affectedRows: index !== -1 ? 1 : 0 };
}
reset() {
this.tables.clear();
this.autoIncrement.clear();
}
}
// ============================================
// PART 3: Integration Test Examples
// ============================================
/**
* Integration Test Suite for UserService + UserRepository
*/
async function runUserIntegrationTests() {
console.log('=== User Integration Tests ===\n');
// Setup
const db = new InMemoryDatabase();
db.createTable('users', ['id', 'name', 'email', 'password']);
const userRepo = new UserRepository(db);
// Track sent emails
const sentEmails = [];
const emailService = {
send: async (to, subject, body) => {
sentEmails.push({ to, subject, body });
return true;
},
};
const validator = {
validate: (data) => {
const errors = [];
if (!data.name || data.name.length < 2) {
errors.push('Name must be at least 2 characters');
}
if (!data.email || !data.email.includes('@')) {
errors.push('Invalid email');
}
if (!data.password || data.password.length < 6) {
errors.push('Password must be at least 6 characters');
}
return { valid: errors.length === 0, errors };
},
};
const userService = new UserService(userRepo, emailService, validator);
// Test 1: Full registration flow
console.log('Test 1: Full registration flow');
try {
const user = await userService.registerUser({
name: 'John Doe',
email: 'john@example.com',
password: 'secret123',
});
console.assert(user.id === 1, 'Should create user with ID');
console.assert(user.name === 'John Doe', 'Should have correct name');
console.assert(!user.password, 'Should not return password');
// Verify email was sent
console.assert(sentEmails.length === 1, 'Should send welcome email');
console.assert(
sentEmails[0].to === 'john@example.com',
'Should send to correct email'
);
// Verify user in database
const dbUser = await userRepo.findById(1);
console.assert(dbUser !== null, 'User should be in database');
console.assert(
dbUser.password !== 'secret123',
'Password should be hashed'
);
console.log(' ✓ User registered successfully');
console.log(' ✓ Welcome email sent');
console.log(' ✓ Password hashed in database');
} catch (e) {
console.log(' ✗ Error:', e.message);
}
// Test 2: Prevent duplicate email registration
console.log('\nTest 2: Prevent duplicate email registration');
try {
await userService.registerUser({
name: 'Jane Doe',
email: 'john@example.com', // Same email
password: 'secret456',
});
console.log(' ✗ Should have thrown error');
} catch (e) {
console.assert(
e.message.includes('already registered'),
'Should indicate email exists'
);
console.log(' ✓ Correctly rejected duplicate email');
}
// Test 3: Validation integration
console.log('\nTest 3: Validation integration');
try {
await userService.registerUser({
name: 'J',
email: 'invalid',
password: '123',
});
console.log(' ✗ Should have thrown validation error');
} catch (e) {
console.assert(e.message.includes('Name'), 'Should include name error');
console.log(' ✓ Validation errors returned correctly');
}
// Test 4: Get user profile
console.log('\nTest 4: Get user profile');
try {
const profile = await userService.getUserProfile(1);
console.assert(profile.name === 'John Doe', 'Should return name');
console.assert(profile.email === 'john@example.com', 'Should return email');
console.assert(!profile.password, 'Should not return password');
console.log(' ✓ Profile retrieved correctly');
} catch (e) {
console.log(' ✗ Error:', e.message);
}
// Test 5: User not found
console.log('\nTest 5: User not found');
try {
await userService.getUserProfile(999);
console.log(' ✗ Should have thrown error');
} catch (e) {
console.assert(e.message === 'User not found', 'Correct error message');
console.log(' ✓ User not found handled correctly');
}
console.log('\n=== Integration Tests Complete ===\n');
}
// ============================================
// PART 4: API Integration Testing Pattern
// ============================================
/**
* Mock HTTP Server for API testing
*/
class MockHTTPServer {
constructor() {
this.routes = new Map();
this.middleware = [];
this.requests = [];
}
use(fn) {
this.middleware.push(fn);
}
get(path, handler) {
this.routes.set(`GET:${path}`, handler);
}
post(path, handler) {
this.routes.set(`POST:${path}`, handler);
}
put(path, handler) {
this.routes.set(`PUT:${path}`, handler);
}
delete(path, handler) {
this.routes.set(`DELETE:${path}`, handler);
}
async request(method, path, options = {}) {
const key = `${method}:${path}`;
const handler = this.routes.get(key);
// Create request/response objects
const req = {
method,
path,
body: options.body || {},
headers: options.headers || {},
params: {},
query: {},
};
const res = {
statusCode: 200,
body: null,
headers: {},
status(code) {
this.statusCode = code;
return this;
},
json(data) {
this.body = data;
this.headers['Content-Type'] = 'application/json';
return this;
},
send(data) {
this.body = data;
return this;
},
};
// Track request
this.requests.push({ method, path, options, timestamp: Date.now() });
// Run middleware
for (const mw of this.middleware) {
await mw(req, res, () => {});
}
// Run handler
if (handler) {
await handler(req, res);
} else {
res.status(404).json({ error: 'Not found' });
}
return {
status: res.statusCode,
body: res.body,
headers: res.headers,
};
}
getRequests() {
return this.requests;
}
clearRequests() {
this.requests = [];
}
}
/**
* API Integration Test Example
*/
function setupUserAPI(app, userService) {
// GET /api/users/:id
app.get('/api/users/:id', async (req, res) => {
try {
const user = await userService.getUserProfile(req.params.id);
res.json(user);
} catch (e) {
if (e.message === 'User not found') {
res.status(404).json({ error: e.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// POST /api/users
app.post('/api/users', async (req, res) => {
try {
const user = await userService.registerUser(req.body);
res.status(201).json(user);
} catch (e) {
res.status(400).json({ error: e.message });
}
});
}
async function runAPIIntegrationTests() {
console.log('=== API Integration Tests ===\n');
// Setup complete system
const db = new InMemoryDatabase();
db.createTable('users', ['id', 'name', 'email', 'password']);
const userRepo = new UserRepository(db);
const emailService = { send: async () => true };
const validator = {
validate: () => ({ valid: true, errors: [] }),
};
const userService = new UserService(userRepo, emailService, validator);
const app = new MockHTTPServer();
// Simple path parameter extraction middleware
app.use((req, res, next) => {
const pathParts = req.path.split('/');
if (pathParts[pathParts.length - 1].match(/^\d+$/)) {
req.params.id = parseInt(pathParts[pathParts.length - 1]);
}
next();
});
// Setup API routes
// Note: Using inline handlers since our mock doesn't support path params natively
app.post('/api/users', async (req, res) => {
try {
const user = await userService.registerUser(req.body);
res.status(201).json(user);
} catch (e) {
res.status(400).json({ error: e.message });
}
});
app.get('/api/users/1', async (req, res) => {
try {
const user = await userService.getUserProfile(1);
res.json(user);
} catch (e) {
if (e.message === 'User not found') {
res.status(404).json({ error: e.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
app.get('/api/users/999', async (req, res) => {
try {
const user = await userService.getUserProfile(999);
res.json(user);
} catch (e) {
if (e.message === 'User not found') {
res.status(404).json({ error: e.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// Test 1: Create user via API
console.log('Test 1: POST /api/users - Create user');
const createResponse = await app.request('POST', '/api/users', {
body: {
name: 'John Doe',
email: 'john@example.com',
password: 'secret123',
},
});
console.assert(createResponse.status === 201, 'Should return 201');
console.assert(createResponse.body.id === 1, 'Should return user ID');
console.assert(createResponse.body.name === 'John Doe', 'Should return name');
console.log(' ✓ User created successfully');
console.log(` Status: ${createResponse.status}`);
console.log(` Body: ${JSON.stringify(createResponse.body)}`);
// Test 2: Get user via API
console.log('\nTest 2: GET /api/users/1 - Get user');
const getResponse = await app.request('GET', '/api/users/1');
console.assert(getResponse.status === 200, 'Should return 200');
console.assert(
getResponse.body.email === 'john@example.com',
'Should return email'
);
console.log(' ✓ User retrieved successfully');
console.log(` Status: ${getResponse.status}`);
console.log(` Body: ${JSON.stringify(getResponse.body)}`);
// Test 3: Get non-existent user
console.log('\nTest 3: GET /api/users/999 - User not found');
const notFoundResponse = await app.request('GET', '/api/users/999');
console.assert(notFoundResponse.status === 404, 'Should return 404');
console.assert(
notFoundResponse.body.error === 'User not found',
'Should return error'
);
console.log(' ✓ 404 returned correctly');
console.log(` Status: ${notFoundResponse.status}`);
console.log(` Body: ${JSON.stringify(notFoundResponse.body)}`);
console.log('\n=== API Tests Complete ===\n');
}
// ============================================
// PART 5: E2E Testing Simulation
// ============================================
/**
* Simulated Browser for E2E Testing
* (Demonstrates concepts - use Playwright/Puppeteer in real tests)
*/
class SimulatedBrowser {
constructor() {
this.currentURL = '';
this.dom = new Map();
this.cookies = new Map();
this.localStorage = new Map();
this.events = [];
}
async goto(url) {
this.currentURL = url;
this.events.push({ type: 'navigate', url });
// Simulate page load
await this.delay(100);
return this;
}
setDOM(elements) {
this.dom = new Map(Object.entries(elements));
}
async $(selector) {
await this.delay(10);
return this.dom.get(selector) || null;
}
async $$(selector) {
await this.delay(10);
const results = [];
for (const [key, value] of this.dom) {
if (key.includes(selector.replace('.', '').replace('#', ''))) {
results.push(value);
}
}
return results;
}
async click(selector) {
const element = await this.$(selector);
if (!element) throw new Error(`Element not found: ${selector}`);
this.events.push({ type: 'click', selector });
// Trigger click handler if exists
if (element.onClick) {
await element.onClick();
}
return this;
}
async type(selector, text) {
const element = await this.$(selector);
if (!element) throw new Error(`Element not found: ${selector}`);
element.value = text;
this.events.push({ type: 'type', selector, text });
return this;
}
async waitForSelector(selector, options = {}) {
const timeout = options.timeout || 5000;
const start = Date.now();
while (Date.now() - start < timeout) {
const element = await this.$(selector);
if (element) return element;
await this.delay(100);
}
throw new Error(`Timeout waiting for ${selector}`);
}
async waitForNavigation() {
await this.delay(100);
return this;
}
async screenshot(options = {}) {
this.events.push({ type: 'screenshot', options });
return Buffer.from('fake-image-data');
}
async evaluate(fn) {
return fn();
}
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
getEvents() {
return this.events;
}
}
/**
* Page Object Pattern Example
*/
class LoginPage {
constructor(browser) {
this.browser = browser;
this.selectors = {
emailInput: '#email',
passwordInput: '#password',
submitButton: '#login-button',
errorMessage: '.error-message',
successMessage: '.success-message',
};
}
async navigate() {
await this.browser.goto('https://example.com/login');
return this;
}
async login(email, password) {
await this.browser.type(this.selectors.emailInput, email);
await this.browser.type(this.selectors.passwordInput, password);
await this.browser.click(this.selectors.submitButton);
await this.browser.waitForNavigation();
return this;
}
async getErrorMessage() {
const element = await this.browser.$(this.selectors.errorMessage);
return element ? element.textContent : null;
}
async isLoggedIn() {
try {
await this.browser.waitForSelector(this.selectors.successMessage, {
timeout: 1000,
});
return true;
} catch {
return false;
}
}
}
class DashboardPage {
constructor(browser) {
this.browser = browser;
this.selectors = {
welcomeMessage: '.welcome',
userMenu: '#user-menu',
logoutButton: '#logout',
};
}
async getWelcomeMessage() {
const element = await this.browser.$(this.selectors.welcomeMessage);
return element ? element.textContent : null;
}
async logout() {
await this.browser.click(this.selectors.userMenu);
await this.browser.click(this.selectors.logoutButton);
await this.browser.waitForNavigation();
return new LoginPage(this.browser);
}
}
/**
* E2E Test Example
*/
async function runE2ETests() {
console.log('=== E2E Test Simulation ===\n');
const browser = new SimulatedBrowser();
// Setup simulated DOM for login page
browser.setDOM({
'#email': { value: '', tagName: 'INPUT' },
'#password': { value: '', tagName: 'INPUT' },
'#login-button': {
tagName: 'BUTTON',
onClick: async () => {
// Simulate successful login
browser.setDOM({
'.success-message': { textContent: 'Login successful' },
'.welcome': { textContent: 'Welcome, John!' },
'#user-menu': { tagName: 'BUTTON' },
'#logout': { tagName: 'BUTTON' },
});
},
},
});
// Test using Page Objects
console.log('Test: Login Flow');
const loginPage = new LoginPage(browser);
await loginPage.navigate();
console.log(' ✓ Navigated to login page');
await loginPage.login('john@example.com', 'secret123');
console.log(' ✓ Filled login form and submitted');
const isLoggedIn = await loginPage.isLoggedIn();
console.assert(isLoggedIn, 'Should be logged in');
console.log(' ✓ Login successful');
const dashboardPage = new DashboardPage(browser);
const welcome = await dashboardPage.getWelcomeMessage();
console.assert(welcome === 'Welcome, John!', 'Should show welcome message');
console.log(` ✓ Dashboard shows: "${welcome}"`);
// Log all browser events
console.log('\nBrowser Events:');
browser.getEvents().forEach((event) => {
console.log(` - ${event.type}: ${event.selector || event.url || ''}`);
});
console.log('\n=== E2E Tests Complete ===\n');
}
// ============================================
// PART 6: Test Fixture Pattern
// ============================================
/**
* Test Data Fixtures
*/
const fixtures = {
users: {
validUser: {
name: 'John Doe',
email: 'john@example.com',
password: 'validPassword123',
},
adminUser: {
name: 'Admin',
email: 'admin@example.com',
password: 'adminPassword123',
role: 'admin',
},
invalidUser: {
name: 'J',
email: 'invalid',
password: '123',
},
},
products: {
widget: {
id: 1,
name: 'Widget',
price: 9.99,
stock: 100,
},
gadget: {
id: 2,
name: 'Gadget',
price: 19.99,
stock: 50,
},
outOfStock: {
id: 3,
name: 'Rare Item',
price: 99.99,
stock: 0,
},
},
orders: {
pending: {
id: 1,
userId: 1,
status: 'pending',
items: [{ productId: 1, quantity: 2 }],
total: 19.98,
},
completed: {
id: 2,
userId: 1,
status: 'completed',
items: [{ productId: 2, quantity: 1 }],
total: 19.99,
},
},
};
/**
* Test Data Factory
*/
class TestFactory {
static createUser(overrides = {}) {
return {
id: TestFactory.generateId(),
name: `Test User ${Date.now()}`,
email: `test${Date.now()}@example.com`,
password: 'defaultPassword',
createdAt: new Date().toISOString(),
...overrides,
};
}
static createProduct(overrides = {}) {
return {
id: TestFactory.generateId(),
name: `Product ${Date.now()}`,
price: Math.random() * 100,
stock: Math.floor(Math.random() * 1000),
...overrides,
};
}
static createOrder(userId, items = [], overrides = {}) {
return {
id: TestFactory.generateId(),
userId,
status: 'pending',
items,
total: items.reduce((sum, item) => sum + item.price * item.quantity, 0),
createdAt: new Date().toISOString(),
...overrides,
};
}
static generateId() {
return Math.floor(Math.random() * 1000000);
}
}
function demonstrateFixtures() {
console.log('=== Test Fixtures Demo ===\n');
// Using fixtures
console.log('Static Fixture:');
console.log(JSON.stringify(fixtures.users.validUser, null, 2));
// Using factory
console.log('\nFactory-generated User:');
const user = TestFactory.createUser({ role: 'admin' });
console.log(JSON.stringify(user, null, 2));
console.log('\nFactory-generated Product:');
const product = TestFactory.createProduct({ name: 'Custom Widget' });
console.log(JSON.stringify(product, null, 2));
console.log('\n=== Fixtures Demo Complete ===\n');
}
// ============================================
// RUN ALL TESTS
// ============================================
async function runAllTests() {
console.log('╔════════════════════════════════════════════╗');
console.log('║ Integration & E2E Testing Examples ║');
console.log('╚════════════════════════════════════════════╝\n');
await runUserIntegrationTests();
await runAPIIntegrationTests();
await runE2ETests();
demonstrateFixtures();
console.log('All examples completed!');
}
// Run
runAllTests();
// Export for use
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
UserRepository,
UserService,
InMemoryDatabase,
MockHTTPServer,
SimulatedBrowser,
LoginPage,
DashboardPage,
TestFactory,
fixtures,
};
}