Backend Testing - 1/2
From Secure Authentication to Reliable Code
You’ve mastered sophisticated identity systems with authentication and authorization frameworks that handle complex organizational hierarchies, enterprise SSO integration, and advanced security practices. Your applications can verify user identity through multiple methods, enforce granular permissions with RBAC systems, and maintain compliance through automated threat detection. But here’s the quality reality that separates working code from production-ready software: even with perfect authentication, bulletproof authorization, and enterprise-grade security, your application is worthless if users can’t trust it to work correctly every single time they interact with it.
The testing nightmare that destroys user confidence:
// Your perfectly authenticated and authorized API with hidden quality issues
app.delete(
"/api/projects/:projectId",
authenticateUser,
rbac.requirePermission("project:delete"),
async (req, res) => {
try {
// Looks simple and secure, but what about edge cases?
const project = await getProject(req.params.projectId);
await deleteProject(req.params.projectId);
res.json({ success: true });
// Hidden problems that testing would catch:
// - What if project doesn't exist? (500 error instead of 404)
// - What if projectId is malformed? (Database crashes)
// - What if deletion fails partially? (Orphaned data)
// - What if user has permission but project is already deleted?
// - What if concurrent deletion requests happen?
// - What if database connection fails during deletion?
// - What if project has dependencies that prevent deletion?
} catch (error) {
// Generic error handling hides the real problems
res.status(500).json({ error: "Deletion failed" });
}
}
);
// User experience without proper testing:
// - "The app randomly crashes when I delete projects"
// - "Sometimes my data disappears, sometimes it doesn't"
// - "I can't trust this system for important work"
// - "Error messages tell me nothing useful"
// - "It works on my machine, but breaks in production"
The uncomfortable testing truth: Security and authentication mean nothing if your core functionality is unreliable. Modern backend applications require comprehensive testing strategies that validate not just happy paths, but the thousands of edge cases, error conditions, and integration scenarios that real users encounter in production environments.
Real-world testing failure consequences:
// What happens when testing is treated as an afterthought:
const testingFailureImpact = {
productionOutages: {
problem: "E-commerce checkout fails during Black Friday surge",
cause: "Payment integration not tested under load",
impact: "$4.2M lost revenue in 6 hours",
detection: "Users complained on social media before monitoring alerts",
fix: "Required emergency rollback and 18-hour incident response",
},
dataCorruption: {
problem: "User accounts randomly lose premium features",
cause: "Database migration script not tested with real data volumes",
impact: "127,000 customers affected, support tickets flooded",
compliance: "Required manual data recovery and compliance reporting",
reputation: "Trust index dropped 34%, 12% customer churn",
},
securityBypass: {
problem: "Authentication bypass in password reset flow",
cause: "Edge case not covered in unit tests",
discovery: "Security researcher found it during penetration testing",
scope: "Potential access to any user account with email enumeration",
cost: "Emergency security audit, legal review, customer notifications",
},
// Perfect architecture is meaningless when basic functionality
// fails under real-world conditions that testing should have caught
};
Backend testing mastery requires understanding:
- Testing pyramid and strategies that balance speed, coverage, and confidence across different test types
- Unit testing with Jest/Mocha that isolates business logic and catches regressions early
- Integration testing that validates how components work together in realistic scenarios
- Database testing patterns that ensure data integrity and query performance
- Mocking and stubbing that enables reliable, fast tests without external dependencies
This article transforms you from code writer to quality engineer. You’ll build testing strategies that catch bugs before users do, create test suites that scale with your codebase, and implement quality gates that prevent regressions from reaching production.
Testing Pyramid: Strategic Test Architecture
Understanding the Testing Hierarchy
The testing strategy that amateur developers get backwards:
// ❌ Common testing anti-pattern: too many slow, brittle tests
describe("E2E Tests", () => {
// 200+ end-to-end tests that take 45 minutes to run
it("should create user and assign role and send email and update dashboard", async () => {
// This one test touches 15+ different components
await browser.goto("/signup");
await browser.fill("#email", "test@example.com");
await browser.click("#submit");
// Wait for email (external service)
await waitForEmail();
// Click email link (another browser session)
await clickEmailLink();
// Complete profile
await completeProfile();
// Admin assigns role
await loginAsAdmin();
await assignUserRole();
// Check dashboard updates
await loginAsUser();
await expectDashboardToShow("Premium Features");
// Problems with this approach:
// - Takes 3+ minutes per test
// - Fails when email service is down
// - Hard to debug which part broke
// - Can't run in parallel
// - Flaky due to timing issues
// - Expensive to maintain
// - Slows down development cycle
});
});
// The backwards pyramid that kills productivity:
// 🔺 E2E Tests: 70% (slow, brittle, expensive)
// 🔸 Integration Tests: 20% (somewhat slow)
// 🔹 Unit Tests: 10% (fast, but not enough coverage)
Professional testing pyramid with strategic distribution:
// ✅ Optimal testing strategy: fast feedback with confidence
class TestingPyramid {
constructor() {
this.strategy = {
// 70% Unit Tests - Fast, isolated, comprehensive
unit: {
percentage: 70,
purpose: "Test business logic, edge cases, error conditions",
speed: "milliseconds",
reliability: "very high",
maintenance: "low",
},
// 20% Integration Tests - Components working together
integration: {
percentage: 20,
purpose:
"Test component interactions, API contracts, database operations",
speed: "seconds",
reliability: "high",
maintenance: "medium",
},
// 10% End-to-End Tests - Critical user journeys only
e2e: {
percentage: 10,
purpose: "Test complete user workflows, deployment verification",
speed: "minutes",
reliability: "medium",
maintenance: "high",
},
};
}
// Unit Test Example: Business Logic
testBusinessLogic() {
return `
// Fast, comprehensive unit tests for core business rules
describe('SubscriptionManager', () => {
let subscriptionManager;
beforeEach(() => {
subscriptionManager = new SubscriptionManager();
});
describe('calculateProration', () => {
it('calculates correct proration for mid-month upgrade', () => {
const result = subscriptionManager.calculateProration({
currentPlan: 'basic', // $10/month
newPlan: 'premium', // $30/month
daysRemaining: 15,
daysInMonth: 30
});
// Expected: ($30 - $10) * (15/30) = $10
expect(result.proratedAmount).toBe(10);
expect(result.effectiveDate).toBeInstanceOf(Date);
});
it('handles edge case: upgrade on last day of month', () => {
const result = subscriptionManager.calculateProration({
currentPlan: 'basic',
newPlan: 'premium',
daysRemaining: 1,
daysInMonth: 31
});
expect(result.proratedAmount).toBe(0.65); // Rounded to cents
});
it('throws error for invalid plan transitions', () => {
expect(() => {
subscriptionManager.calculateProration({
currentPlan: 'premium',
newPlan: 'nonexistent-plan',
daysRemaining: 15,
daysInMonth: 30
});
}).toThrow('Invalid plan transition');
});
it('handles leap year february correctly', () => {
const result = subscriptionManager.calculateProration({
currentPlan: 'basic',
newPlan: 'premium',
daysRemaining: 14,
daysInMonth: 29 // Leap year February
});
expect(result.proratedAmount).toBe(13.79);
});
});
describe('validateSubscriptionChange', () => {
it('prevents downgrade if user has premium features in use', async () => {
const mockUser = {
id: 'user123',
currentPlan: 'premium',
premiumFeaturesInUse: ['advanced_analytics', 'custom_branding']
};
const result = await subscriptionManager.validateSubscriptionChange(
mockUser,
'basic'
);
expect(result.allowed).toBe(false);
expect(result.reason).toBe('premium_features_in_use');
expect(result.conflictingFeatures).toEqual(['advanced_analytics', 'custom_branding']);
});
it('allows upgrade without validation', async () => {
const mockUser = {
id: 'user123',
currentPlan: 'basic'
};
const result = await subscriptionManager.validateSubscriptionChange(
mockUser,
'premium'
);
expect(result.allowed).toBe(true);
});
});
});
`;
}
// Integration Test Example: Component Interaction
testComponentIntegration() {
return `
// Integration tests for how components work together
describe('User Registration Flow Integration', () => {
let app;
let db;
let emailService;
beforeEach(async () => {
// Setup test database
db = await createTestDatabase();
// Mock external services
emailService = {
sendWelcomeEmail: jest.fn().mockResolvedValue({ messageId: 'test123' }),
sendVerificationEmail: jest.fn().mockResolvedValue({ messageId: 'test456' })
};
// Create app instance with test dependencies
app = createApp({ database: db, emailService });
});
afterEach(async () => {
await db.cleanup();
});
it('registers new user and sends verification email', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
firstName: 'John',
lastName: 'Doe'
};
// Make registration request
const response = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(201);
// Verify response structure
expect(response.body).toMatchObject({
success: true,
message: 'Registration successful. Please verify your email.',
userId: expect.any(String)
});
// Verify user was created in database
const createdUser = await db.users.findOne({
email: userData.email
});
expect(createdUser).toBeDefined();
expect(createdUser.email).toBe(userData.email);
expect(createdUser.emailVerified).toBe(false);
expect(createdUser.hashedPassword).not.toBe(userData.password);
// Verify verification email was sent
expect(emailService.sendVerificationEmail).toHaveBeenCalledWith({
email: userData.email,
firstName: userData.firstName,
verificationToken: expect.any(String)
});
// Verify verification token was stored
const verificationToken = await db.verificationTokens.findOne({
userId: createdUser.id
});
expect(verificationToken).toBeDefined();
expect(verificationToken.type).toBe('email_verification');
expect(verificationToken.expiresAt).toBeAfter(new Date());
});
it('handles duplicate email registration gracefully', async () => {
// Create existing user
await db.users.insertOne({
id: 'existing123',
email: 'test@example.com',
hashedPassword: 'hashedpass'
});
const userData = {
email: 'test@example.com', // Same email
password: 'DifferentPass123!',
firstName: 'Jane',
lastName: 'Smith'
};
const response = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(409); // Conflict
expect(response.body).toMatchObject({
error: 'Email already registered',
message: 'An account with this email already exists'
});
// Verify no duplicate user was created
const userCount = await db.users.countDocuments({
email: userData.email
});
expect(userCount).toBe(1);
// Verify no email was sent for duplicate registration
expect(emailService.sendVerificationEmail).not.toHaveBeenCalled();
});
it('rolls back user creation if email service fails', async () => {
// Mock email service failure
emailService.sendVerificationEmail.mockRejectedValue(
new Error('Email service unavailable')
);
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
firstName: 'John',
lastName: 'Doe'
};
const response = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(503); // Service unavailable
expect(response.body).toMatchObject({
error: 'Registration temporarily unavailable',
message: 'Please try again in a few minutes'
});
// Verify user was NOT created due to rollback
const userCount = await db.users.countDocuments({
email: userData.email
});
expect(userCount).toBe(0);
// Verify no orphaned verification tokens
const tokenCount = await db.verificationTokens.countDocuments();
expect(tokenCount).toBe(0);
});
});
`;
}
// E2E Test Example: Critical User Journey
testCriticalUserJourney() {
return `
// Minimal, focused E2E tests for critical paths only
describe('Critical User Journeys', () => {
it('complete signup to first premium feature usage', async () => {
// This test covers the most important user flow
// From signup -> verification -> subscription -> feature access
const page = await browser.newPage();
try {
// 1. User signs up
await page.goto('/signup');
await page.fill('[data-testid="email"]', 'premium-test@example.com');
await page.fill('[data-testid="password"]', 'SecurePass123!');
await page.click('[data-testid="signup-btn"]');
await page.waitForSelector('[data-testid="verify-email-message"]');
// 2. Verify email (mock email service returns token)
const verificationLink = await getVerificationLinkFromTestEmail();
await page.goto(verificationLink);
await page.waitForSelector('[data-testid="email-verified-success"]');
// 3. Choose premium plan
await page.click('[data-testid="choose-premium-plan"]');
await page.fill('[data-testid="card-number"]', '4242424242424242');
await page.fill('[data-testid="card-expiry"]', '12/25');
await page.fill('[data-testid="card-cvc"]', '123');
await page.click('[data-testid="complete-payment"]');
// Wait for payment processing
await page.waitForSelector('[data-testid="payment-success"]', { timeout: 10000 });
// 4. Access premium feature immediately
await page.click('[data-testid="advanced-analytics"]');
// Verify premium feature loads correctly
await expect(page.locator('[data-testid="premium-dashboard"]')).toBeVisible();
await expect(page.locator('[data-testid="premium-badge"]')).toContainText('Premium');
// Verify feature actually works
await page.click('[data-testid="generate-report"]');
await page.waitForSelector('[data-testid="report-generated"]');
} finally {
await page.close();
// Cleanup test data
await cleanupTestUser('premium-test@example.com');
}
});
});
`;
}
}
// Testing strategy implementation
class TestingStrategy {
constructor() {
this.testSuites = {
unit: new UnitTestSuite(),
integration: new IntegrationTestSuite(),
e2e: new E2ETestSuite(),
};
}
async runTestPyramid() {
console.log("🔹 Running Unit Tests (70% coverage)...");
const unitResults = await this.testSuites.unit.run();
if (unitResults.failed > 0) {
throw new Error("Unit tests must pass before integration tests");
}
console.log("🔸 Running Integration Tests (20% coverage)...");
const integrationResults = await this.testSuites.integration.run();
if (integrationResults.failed > 0) {
throw new Error("Integration tests must pass before E2E tests");
}
console.log("🔺 Running E2E Tests (10% coverage)...");
const e2eResults = await this.testSuites.e2e.run();
return {
unit: unitResults,
integration: integrationResults,
e2e: e2eResults,
totalCoverage: this.calculateOverallCoverage(),
};
}
}
Unit Testing with Jest/Mocha: Isolated Logic Validation
Comprehensive Unit Testing Implementation
The unit test setup that catches bugs before integration:
// ✅ Professional Jest configuration for backend testing
// jest.config.js
module.exports = {
// Test environment
testEnvironment: "node",
// Test file patterns
testMatch: ["**/__tests__/**/*.js", "**/*.test.js", "**/*.spec.js"],
// Coverage configuration
collectCoverage: true,
coverageDirectory: "coverage",
collectCoverageFrom: [
"src/**/*.js",
"!src/**/*.test.js",
"!src/config/**",
"!src/migrations/**",
],
// Coverage thresholds - enforce quality gates
coverageThreshold: {
global: {
branches: 80,
functions: 85,
lines: 85,
statements: 85,
},
"./src/services/": {
branches: 90,
functions: 95,
lines: 95,
statements: 95,
},
},
// Setup files
setupFilesAfterEnv: ["<rootDir>/tests/setup.js"],
// Module resolution
moduleNameMapping: {
"^@/(.*)$": "<rootDir>/src/$1",
"^@tests/(.*)$": "<rootDir>/tests/$1",
},
// Test timeout
testTimeout: 10000,
};
// tests/setup.js - Global test configuration
const { MongoMemoryServer } = require("mongodb-memory-server");
// Global test setup
beforeAll(async () => {
// Setup in-memory database for tests
global.__MONGOD__ = await MongoMemoryServer.create();
process.env.DATABASE_URL = global.__MONGOD__.getUri();
// Mock external services
jest.mock("nodemailer");
jest.mock("stripe");
jest.mock("redis");
// Set test environment variables
process.env.NODE_ENV = "test";
process.env.JWT_SECRET = "test-secret";
});
// Global test cleanup
afterAll(async () => {
if (global.__MONGOD__) {
await global.__MONGOD__.stop();
}
});
// Custom matchers for better assertions
expect.extend({
toBeValidEmail(received) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const pass = emailRegex.test(received);
return {
message: () =>
pass
? `Expected ${received} not to be a valid email`
: `Expected ${received} to be a valid email`,
pass,
};
},
toBeAfter(received, date) {
const pass = received > date;
return {
message: () =>
pass
? `Expected ${received} not to be after ${date}`
: `Expected ${received} to be after ${date}`,
pass,
};
},
});
Professional unit test examples with comprehensive coverage:
// ✅ Service layer unit tests with dependency injection
// tests/services/UserService.test.js
describe("UserService", () => {
let userService;
let mockUserRepository;
let mockEmailService;
let mockPasswordService;
beforeEach(() => {
// Create mocks for dependencies
mockUserRepository = {
findByEmail: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
findByResetToken: jest.fn(),
};
mockEmailService = {
sendWelcomeEmail: jest.fn(),
sendPasswordResetEmail: jest.fn(),
sendAccountLockedEmail: jest.fn(),
};
mockPasswordService = {
hash: jest.fn(),
compare: jest.fn(),
generateResetToken: jest.fn(),
validatePasswordStrength: jest.fn(),
};
// Inject mocks into service
userService = new UserService({
userRepository: mockUserRepository,
emailService: mockEmailService,
passwordService: mockPasswordService,
});
});
afterEach(() => {
jest.clearAllMocks();
});
describe("createUser", () => {
it("creates user with valid data", async () => {
// Arrange
const userData = {
email: "test@example.com",
password: "SecurePass123!",
firstName: "John",
lastName: "Doe",
};
const hashedPassword = "hashed-password";
const createdUser = {
id: "user123",
...userData,
password: hashedPassword,
createdAt: new Date(),
emailVerified: false,
};
mockUserRepository.findByEmail.mockResolvedValue(null); // No existing user
mockPasswordService.validatePasswordStrength.mockReturnValue({
valid: true,
});
mockPasswordService.hash.mockResolvedValue(hashedPassword);
mockUserRepository.create.mockResolvedValue(createdUser);
mockEmailService.sendWelcomeEmail.mockResolvedValue({
messageId: "email123",
});
// Act
const result = await userService.createUser(userData);
// Assert
expect(mockUserRepository.findByEmail).toHaveBeenCalledWith(
userData.email
);
expect(mockPasswordService.validatePasswordStrength).toHaveBeenCalledWith(
userData.password
);
expect(mockPasswordService.hash).toHaveBeenCalledWith(userData.password);
expect(mockUserRepository.create).toHaveBeenCalledWith({
...userData,
password: hashedPassword,
emailVerified: false,
});
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith({
email: userData.email,
firstName: userData.firstName,
});
expect(result).toMatchObject({
id: "user123",
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
});
expect(result.password).toBeUndefined(); // Password should not be in response
});
it("throws error when user already exists", async () => {
// Arrange
const userData = {
email: "existing@example.com",
password: "SecurePass123!",
};
const existingUser = {
id: "existing123",
email: userData.email,
};
mockUserRepository.findByEmail.mockResolvedValue(existingUser);
// Act & Assert
await expect(userService.createUser(userData)).rejects.toThrow(
"User with this email already exists"
);
expect(mockPasswordService.hash).not.toHaveBeenCalled();
expect(mockUserRepository.create).not.toHaveBeenCalled();
expect(mockEmailService.sendWelcomeEmail).not.toHaveBeenCalled();
});
it("throws error for weak password", async () => {
// Arrange
const userData = {
email: "test@example.com",
password: "123", // Weak password
};
mockUserRepository.findByEmail.mockResolvedValue(null);
mockPasswordService.validatePasswordStrength.mockReturnValue({
valid: false,
errors: ["Password must be at least 8 characters"],
});
// Act & Assert
await expect(userService.createUser(userData)).rejects.toThrow(
"Password does not meet security requirements"
);
expect(mockPasswordService.hash).not.toHaveBeenCalled();
expect(mockUserRepository.create).not.toHaveBeenCalled();
});
it("rolls back user creation if email sending fails", async () => {
// Arrange
const userData = {
email: "test@example.com",
password: "SecurePass123!",
firstName: "John",
};
const createdUser = { id: "user123", ...userData };
mockUserRepository.findByEmail.mockResolvedValue(null);
mockPasswordService.validatePasswordStrength.mockReturnValue({
valid: true,
});
mockPasswordService.hash.mockResolvedValue("hashed");
mockUserRepository.create.mockResolvedValue(createdUser);
mockEmailService.sendWelcomeEmail.mockRejectedValue(
new Error("Email service down")
);
mockUserRepository.delete.mockResolvedValue();
// Act & Assert
await expect(userService.createUser(userData)).rejects.toThrow(
"Failed to send welcome email"
);
// Verify rollback happened
expect(mockUserRepository.delete).toHaveBeenCalledWith("user123");
});
});
describe("authenticateUser", () => {
it("authenticates user with correct credentials", async () => {
// Arrange
const email = "test@example.com";
const password = "SecurePass123!";
const hashedPassword = "hashed-password";
const user = {
id: "user123",
email,
password: hashedPassword,
loginAttempts: 0,
lockUntil: null,
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
mockPasswordService.compare.mockResolvedValue(true);
mockUserRepository.update.mockResolvedValue();
// Act
const result = await userService.authenticateUser(email, password);
// Assert
expect(mockUserRepository.findByEmail).toHaveBeenCalledWith(email);
expect(mockPasswordService.compare).toHaveBeenCalledWith(
password,
hashedPassword
);
expect(mockUserRepository.update).toHaveBeenCalledWith("user123", {
loginAttempts: 0,
lastLoginAt: expect.any(Date),
});
expect(result).toMatchObject({
id: "user123",
email,
});
expect(result.password).toBeUndefined();
});
it("increments login attempts on wrong password", async () => {
// Arrange
const email = "test@example.com";
const password = "WrongPassword";
const user = {
id: "user123",
email,
password: "correct-hash",
loginAttempts: 2,
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
mockPasswordService.compare.mockResolvedValue(false);
mockUserRepository.update.mockResolvedValue();
// Act & Assert
await expect(
userService.authenticateUser(email, password)
).rejects.toThrow("Invalid email or password");
expect(mockUserRepository.update).toHaveBeenCalledWith("user123", {
loginAttempts: 3,
});
});
it("locks account after 5 failed attempts", async () => {
// Arrange
const email = "test@example.com";
const password = "WrongPassword";
const user = {
id: "user123",
email,
password: "correct-hash",
loginAttempts: 4, // Will become 5
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
mockPasswordService.compare.mockResolvedValue(false);
mockUserRepository.update.mockResolvedValue();
mockEmailService.sendAccountLockedEmail.mockResolvedValue();
// Act & Assert
await expect(
userService.authenticateUser(email, password)
).rejects.toThrow("Account locked due to too many failed attempts");
expect(mockUserRepository.update).toHaveBeenCalledWith("user123", {
loginAttempts: 5,
lockUntil: expect.any(Date),
});
expect(mockEmailService.sendAccountLockedEmail).toHaveBeenCalledWith({
email,
lockUntil: expect.any(Date),
});
});
it("rejects authentication for locked account", async () => {
// Arrange
const email = "test@example.com";
const password = "CorrectPassword";
const user = {
id: "user123",
email,
password: "correct-hash",
loginAttempts: 5,
lockUntil: new Date(Date.now() + 60000), // Locked for 1 minute
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
// Act & Assert
await expect(
userService.authenticateUser(email, password)
).rejects.toThrow("Account is temporarily locked");
expect(mockPasswordService.compare).not.toHaveBeenCalled();
});
it("unlocks account after lock period expires", async () => {
// Arrange
const email = "test@example.com";
const password = "CorrectPassword";
const user = {
id: "user123",
email,
password: "correct-hash",
loginAttempts: 5,
lockUntil: new Date(Date.now() - 1000), // Lock expired 1 second ago
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
mockPasswordService.compare.mockResolvedValue(true);
mockUserRepository.update.mockResolvedValue();
// Act
const result = await userService.authenticateUser(email, password);
// Assert
expect(mockUserRepository.update).toHaveBeenCalledWith("user123", {
loginAttempts: 0,
lockUntil: null,
lastLoginAt: expect.any(Date),
});
expect(result.id).toBe("user123");
});
});
describe("resetPassword", () => {
it("sends reset email for valid user", async () => {
// Arrange
const email = "test@example.com";
const resetToken = "reset-token-123";
const user = {
id: "user123",
email,
emailVerified: true,
};
mockUserRepository.findByEmail.mockResolvedValue(user);
mockPasswordService.generateResetToken.mockReturnValue(resetToken);
mockUserRepository.update.mockResolvedValue();
mockEmailService.sendPasswordResetEmail.mockResolvedValue();
// Act
const result = await userService.resetPassword(email);
// Assert
expect(mockPasswordService.generateResetToken).toHaveBeenCalled();
expect(mockUserRepository.update).toHaveBeenCalledWith("user123", {
resetToken,
resetTokenExpiry: expect.any(Date),
});
expect(mockEmailService.sendPasswordResetEmail).toHaveBeenCalledWith({
email,
resetToken,
});
expect(result.success).toBe(true);
expect(result.message).toBe("Password reset email sent");
});
it("returns success even for non-existent email (security)", async () => {
// Arrange
const email = "nonexistent@example.com";
mockUserRepository.findByEmail.mockResolvedValue(null);
// Act
const result = await userService.resetPassword(email);
// Assert - Don't reveal that user doesn't exist
expect(result.success).toBe(true);
expect(result.message).toBe("Password reset email sent");
expect(mockPasswordService.generateResetToken).not.toHaveBeenCalled();
expect(mockEmailService.sendPasswordResetEmail).not.toHaveBeenCalled();
});
});
});
Integration Testing: Component Interaction Validation
Database Integration Testing Patterns
Professional database testing with real scenarios:
// ✅ Database integration tests with transaction handling
// tests/integration/UserRepository.test.js
describe("UserRepository Integration", () => {
let db;
let userRepository;
beforeAll(async () => {
// Use test database
db = await connectToTestDatabase();
userRepository = new UserRepository(db);
});
beforeEach(async () => {
// Clean database before each test
await db.users.deleteMany({});
await db.userProfiles.deleteMany({});
await db.auditLogs.deleteMany({});
});
afterAll(async () => {
await db.close();
});
describe("createUserWithProfile", () => {
it("creates user and profile in single transaction", async () => {
// Arrange
const userData = {
email: "test@example.com",
password: "hashedPassword123",
firstName: "John",
lastName: "Doe",
};
const profileData = {
bio: "Software developer",
location: "San Francisco",
skills: ["JavaScript", "Node.js", "React"],
};
// Act
const result = await userRepository.createUserWithProfile(
userData,
profileData
);
// Assert - User created
expect(result.user.id).toBeDefined();
expect(result.user.email).toBe(userData.email);
// Assert - Profile created with correct user reference
expect(result.profile.userId).toBe(result.user.id);
expect(result.profile.bio).toBe(profileData.bio);
// Verify in database
const dbUser = await db.users.findOne({ id: result.user.id });
const dbProfile = await db.userProfiles.findOne({
userId: result.user.id,
});
expect(dbUser).toBeDefined();
expect(dbProfile).toBeDefined();
expect(dbProfile.skills).toEqual(profileData.skills);
});
it("rolls back both user and profile if profile creation fails", async () => {
// Arrange
const userData = {
email: "test@example.com",
password: "hashedPassword123",
};
const invalidProfileData = {
bio: "x".repeat(5001), // Exceeds max length, will cause constraint violation
};
// Act & Assert
await expect(
userRepository.createUserWithProfile(userData, invalidProfileData)
).rejects.toThrow("Profile bio exceeds maximum length");
// Verify rollback - neither user nor profile should exist
const userCount = await db.users.countDocuments();
const profileCount = await db.userProfiles.countDocuments();
expect(userCount).toBe(0);
expect(profileCount).toBe(0);
});
it("handles concurrent user creation with same email", async () => {
// Arrange
const userData1 = {
email: "same@example.com",
password: "hashedPassword1",
};
const userData2 = {
email: "same@example.com",
password: "hashedPassword2",
};
const profileData = { bio: "Test bio" };
// Act - Attempt concurrent creation
const [result1, result2] = await Promise.allSettled([
userRepository.createUserWithProfile(userData1, profileData),
userRepository.createUserWithProfile(userData2, profileData),
]);
// Assert - One succeeds, one fails
const successes = [result1, result2].filter(
(r) => r.status === "fulfilled"
);
const failures = [result1, result2].filter(
(r) => r.status === "rejected"
);
expect(successes).toHaveLength(1);
expect(failures).toHaveLength(1);
expect(failures[0].reason.message).toMatch(
/duplicate key error|email already exists/i
);
// Verify only one user exists
const userCount = await db.users.countDocuments({
email: "same@example.com",
});
expect(userCount).toBe(1);
});
});
describe("updateUserWithAudit", () => {
it("updates user and creates audit log", async () => {
// Arrange
const user = await db.users.insertOne({
id: "user123",
email: "test@example.com",
firstName: "John",
lastName: "Doe",
version: 1,
});
const updates = {
firstName: "Jane",
lastName: "Smith",
};
const auditContext = {
updatedBy: "admin123",
reason: "User requested name change",
ipAddress: "192.168.1.100",
};
// Act
const result = await userRepository.updateUserWithAudit(
"user123",
updates,
auditContext
);
// Assert - User updated
expect(result.firstName).toBe("Jane");
expect(result.lastName).toBe("Smith");
expect(result.version).toBe(2); // Version incremented
// Verify audit log created
const auditLogs = await db.auditLogs
.find({
entityType: "user",
entityId: "user123",
})
.toArray();
expect(auditLogs).toHaveLength(1);
expect(auditLogs[0]).toMatchObject({
action: "update",
changes: {
firstName: { from: "John", to: "Jane" },
lastName: { from: "Doe", to: "Smith" },
},
updatedBy: "admin123",
reason: "User requested name change",
ipAddress: "192.168.1.100",
});
});
it("prevents concurrent updates using optimistic locking", async () => {
// Arrange
await db.users.insertOne({
id: "user123",
email: "test@example.com",
firstName: "John",
version: 1,
});
// Act - Simulate concurrent updates
const update1Promise = userRepository.updateUserWithAudit(
"user123",
{ firstName: "Jane" },
{ updatedBy: "user1" }
);
const update2Promise = userRepository.updateUserWithAudit(
"user123",
{ firstName: "Bob" },
{ updatedBy: "user2" }
);
const [result1, result2] = await Promise.allSettled([
update1Promise,
update2Promise,
]);
// Assert - One succeeds, one fails due to version conflict
const successes = [result1, result2].filter(
(r) => r.status === "fulfilled"
);
const failures = [result1, result2].filter(
(r) => r.status === "rejected"
);
expect(successes).toHaveLength(1);
expect(failures).toHaveLength(1);
expect(failures[0].reason.message).toMatch(
/version conflict|concurrent modification/i
);
});
});
describe("searchUsers", () => {
beforeEach(async () => {
// Setup test data
const testUsers = [
{
id: "user1",
email: "john.doe@example.com",
firstName: "John",
lastName: "Doe",
department: "Engineering",
role: "Developer",
createdAt: new Date("2023-01-01"),
isActive: true,
},
{
id: "user2",
email: "jane.smith@example.com",
firstName: "Jane",
lastName: "Smith",
department: "Marketing",
role: "Manager",
createdAt: new Date("2023-02-01"),
isActive: true,
},
{
id: "user3",
email: "bob.wilson@example.com",
firstName: "Bob",
lastName: "Wilson",
department: "Engineering",
role: "Senior Developer",
createdAt: new Date("2023-03-01"),
isActive: false,
},
];
await db.users.insertMany(testUsers);
});
it("searches users by text query", async () => {
// Act
const results = await userRepository.searchUsers({
query: "john",
limit: 10,
offset: 0,
});
// Assert
expect(results.users).toHaveLength(1);
expect(results.users[0].firstName).toBe("John");
expect(results.total).toBe(1);
});
it("filters by department and role", async () => {
// Act
const results = await userRepository.searchUsers({
filters: {
department: "Engineering",
role: "Developer",
},
limit: 10,
offset: 0,
});
// Assert
expect(results.users).toHaveLength(1);
expect(results.users[0].firstName).toBe("John");
});
it("excludes inactive users by default", async () => {
// Act
const results = await userRepository.searchUsers({
filters: {
department: "Engineering",
},
});
// Assert - Only active users returned
expect(results.users).toHaveLength(1);
expect(results.users[0].isActive).toBe(true);
});
it("includes inactive users when specified", async () => {
// Act
const results = await userRepository.searchUsers({
filters: {
department: "Engineering",
includeInactive: true,
},
});
// Assert - Both active and inactive users
expect(results.users).toHaveLength(2);
});
it("handles pagination correctly", async () => {
// Act - Get first page
const page1 = await userRepository.searchUsers({
limit: 2,
offset: 0,
});
// Act - Get second page
const page2 = await userRepository.searchUsers({
limit: 2,
offset: 2,
});
// Assert
expect(page1.users).toHaveLength(2);
expect(page2.users).toHaveLength(1); // Only 1 remaining user
expect(page1.total).toBe(3);
expect(page2.total).toBe(3);
// Verify no overlap
const page1Ids = page1.users.map((u) => u.id);
const page2Ids = page2.users.map((u) => u.id);
const intersection = page1Ids.filter((id) => page2Ids.includes(id));
expect(intersection).toHaveLength(0);
});
});
});
Mocking and Stubbing: Dependency Isolation
Advanced Mocking Strategies
Professional mocking for reliable, fast tests:
// ✅ Comprehensive mocking strategies for external dependencies
// tests/mocks/ExternalServices.js
class MockEmailService {
constructor() {
this.sentEmails = [];
this.shouldFail = false;
this.delayMs = 0;
}
async sendEmail({ to, subject, template, data }) {
if (this.delayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, this.delayMs));
}
if (this.shouldFail) {
throw new Error("Email service unavailable");
}
const email = {
id: `email-${Date.now()}`,
to,
subject,
template,
data,
sentAt: new Date(),
};
this.sentEmails.push(email);
return { messageId: email.id };
}
// Test utilities
getLastEmail() {
return this.sentEmails[this.sentEmails.length - 1];
}
getEmailsTo(recipient) {
return this.sentEmails.filter((email) => email.to === recipient);
}
clear() {
this.sentEmails = [];
}
simulateFailure() {
this.shouldFail = true;
}
simulateDelay(ms) {
this.delayMs = ms;
}
}
class MockPaymentService {
constructor() {
this.payments = [];
this.shouldFailPayment = false;
this.failureReason = "card_declined";
}
async processPayment({ amount, currency, source, description }) {
if (this.shouldFailPayment) {
throw new PaymentError(this.failureReason, {
amount,
currency,
source: source.id,
});
}
const payment = {
id: `pay_${Date.now()}`,
amount,
currency,
source,
description,
status: "succeeded",
processedAt: new Date(),
};
this.payments.push(payment);
return payment;
}
async refundPayment(paymentId, amount) {
const payment = this.payments.find((p) => p.id === paymentId);
if (!payment) {
throw new Error("Payment not found");
}
if (amount > payment.amount) {
throw new Error("Refund amount exceeds payment amount");
}
const refund = {
id: `re_${Date.now()}`,
paymentId,
amount,
status: "succeeded",
refundedAt: new Date(),
};
payment.refunded = (payment.refunded || 0) + amount;
return refund;
}
// Test utilities
simulatePaymentFailure(reason = "card_declined") {
this.shouldFailPayment = true;
this.failureReason = reason;
}
getPayment(id) {
return this.payments.find((p) => p.id === id);
}
clear() {
this.payments = [];
this.shouldFailPayment = false;
}
}
// Mock factory for creating configured mock instances
class MockFactory {
static createEmailService(config = {}) {
const mock = new MockEmailService();
if (config.shouldFail) mock.simulateFailure();
if (config.delay) mock.simulateDelay(config.delay);
return mock;
}
static createPaymentService(config = {}) {
const mock = new MockPaymentService();
if (config.shouldFail) {
mock.simulatePaymentFailure(config.failureReason);
}
return mock;
}
// Create pre-configured mock scenarios
static createReliableServices() {
return {
emailService: this.createEmailService(),
paymentService: this.createPaymentService(),
};
}
static createUnreliableServices() {
return {
emailService: this.createEmailService({ shouldFail: true }),
paymentService: this.createPaymentService({ shouldFail: true }),
};
}
static createSlowServices() {
return {
emailService: this.createEmailService({ delay: 1000 }),
paymentService: this.createPaymentService(),
};
}
}
// Integration test using sophisticated mocks
describe("SubscriptionService Integration", () => {
let subscriptionService;
let mockEmailService;
let mockPaymentService;
let db;
beforeEach(async () => {
db = await createTestDatabase();
// Create configured mocks
mockEmailService = MockFactory.createEmailService();
mockPaymentService = MockFactory.createPaymentService();
subscriptionService = new SubscriptionService({
database: db,
emailService: mockEmailService,
paymentService: mockPaymentService,
});
});
afterEach(async () => {
await db.cleanup();
});
describe("upgradeSubscription", () => {
it("successfully upgrades subscription with prorated billing", async () => {
// Arrange
const user = await createTestUser();
const currentSubscription = await createTestSubscription({
userId: user.id,
plan: "basic",
amount: 1000, // $10.00
billingCycle: "monthly",
currentPeriodStart: new Date("2023-01-01"),
currentPeriodEnd: new Date("2023-01-31"),
});
// Mock current date to mid-month
const mockDate = new Date("2023-01-15");
jest.useFakeTimers().setSystemTime(mockDate);
// Act
const result = await subscriptionService.upgradeSubscription({
userId: user.id,
newPlan: "premium",
paymentSource: { id: "card_test123" },
});
// Assert subscription upgraded
expect(result.subscription.plan).toBe("premium");
expect(result.subscription.amount).toBe(3000); // $30.00
// Assert prorated payment processed
const processedPayment = mockPaymentService.getPayment(result.payment.id);
expect(processedPayment).toBeDefined();
expect(processedPayment.amount).toBe(1000); // Prorated amount: ($30-$10) * (16/31)
expect(processedPayment.description).toMatch(/prorated upgrade/i);
// Assert upgrade email sent
const upgradeEmail = mockEmailService.getLastEmail();
expect(upgradeEmail.template).toBe("subscription_upgraded");
expect(upgradeEmail.data.newPlan).toBe("premium");
expect(upgradeEmail.data.proratedAmount).toBe(10.0);
// Verify database state
const updatedSubscription = await db.subscriptions.findOne({
userId: user.id,
});
expect(updatedSubscription.plan).toBe("premium");
expect(updatedSubscription.amount).toBe(3000);
});
it("handles payment failure gracefully", async () => {
// Arrange
const user = await createTestUser();
const currentSubscription = await createTestSubscription({
userId: user.id,
plan: "basic",
});
// Configure payment service to fail
mockPaymentService.simulatePaymentFailure("insufficient_funds");
// Act & Assert
await expect(
subscriptionService.upgradeSubscription({
userId: user.id,
newPlan: "premium",
paymentSource: { id: "card_test123" },
})
).rejects.toThrow("Payment failed: insufficient_funds");
// Verify subscription unchanged
const subscription = await db.subscriptions.findOne({
userId: user.id,
});
expect(subscription.plan).toBe("basic"); // Unchanged
// Verify failure email sent
const failureEmail = mockEmailService.getLastEmail();
expect(failureEmail.template).toBe("payment_failed");
expect(failureEmail.data.reason).toBe("insufficient_funds");
// Verify no successful payment recorded
expect(mockPaymentService.payments).toHaveLength(0);
});
it("handles partial failure with rollback", async () => {
// Arrange
const user = await createTestUser();
const currentSubscription = await createTestSubscription({
userId: user.id,
plan: "basic",
});
// Configure email service to fail after payment succeeds
mockEmailService.simulateFailure();
// Act & Assert
await expect(
subscriptionService.upgradeSubscription({
userId: user.id,
newPlan: "premium",
paymentSource: { id: "card_test123" },
})
).rejects.toThrow("Failed to send upgrade notification");
// Verify payment was refunded automatically
const payment = mockPaymentService.payments[0];
expect(payment.refunded).toBe(payment.amount);
// Verify subscription rolled back
const subscription = await db.subscriptions.findOne({
userId: user.id,
});
expect(subscription.plan).toBe("basic");
});
});
describe("cancelSubscription", () => {
it("processes immediate cancellation with refund", async () => {
// Arrange
const user = await createTestUser();
const subscription = await createTestSubscription({
userId: user.id,
plan: "premium",
amount: 3000,
lastPaymentId: "pay_existing123",
});
// Mock existing payment in payment service
mockPaymentService.payments.push({
id: "pay_existing123",
amount: 3000,
status: "succeeded",
});
// Act
const result = await subscriptionService.cancelSubscription({
userId: user.id,
immediate: true,
reason: "customer_request",
});
// Assert cancellation processed
expect(result.status).toBe("cancelled");
expect(result.cancelledAt).toBeInstanceOf(Date);
// Assert refund processed
const payment = mockPaymentService.getPayment("pay_existing123");
expect(payment.refunded).toBeGreaterThan(0);
// Assert cancellation email sent
const cancellationEmail = mockEmailService.getLastEmail();
expect(cancellationEmail.template).toBe("subscription_cancelled");
expect(cancellationEmail.data.refundAmount).toBeGreaterThan(0);
});
});
});
Key Takeaways
Backend testing transforms unreliable code into production-ready software through strategic test architecture that catches bugs early and prevents regressions. The testing pyramid balances speed with coverage, unit tests validate business logic comprehensively, integration tests ensure components work together, and sophisticated mocking enables reliable testing without external dependencies.
The testing-first mindset:
- Quality gates prevent problems: Comprehensive test suites catch issues before users encounter them
- Test pyramid optimizes feedback: 70% unit tests provide fast feedback, 20% integration tests validate interactions, 10% E2E tests verify critical workflows
- Mocking enables isolation: Well-designed mocks and stubs allow testing complex scenarios without external service dependencies
- Database testing requires strategy: Integration tests with real database operations validate data integrity and query performance
What distinguishes professional testing practices:
- Testing strategies that scale from small functions to complex integration scenarios
- Unit tests that comprehensively cover edge cases, error conditions, and business rule variations
- Integration tests that validate realistic component interactions with proper setup and teardown
- Mock implementations that accurately simulate external service behaviors and failure modes
What’s Next
This article established testing fundamentals with unit testing, integration testing, and mocking strategies. The next article completes the testing picture with API testing, performance testing, test-driven development practices, and continuous testing integration that ensures quality at scale.
Testing catches bugs early. The next level validates performance, automates quality gates, and integrates testing into continuous delivery pipelines that maintain quality as applications grow and teams scale.