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.