Skip to content

Testing Documentation

Comprehensive testing guide for HKFR development.

Testing Philosophy

HKFR maintains a robust testing strategy with 100% test success rate and comprehensive coverage across all critical components.

Testing Pyramid

graph TB
    A[Unit Tests - 80%] --> B[Integration Tests - 15%]
    B --> C[E2E Tests - 5%]

    A --> D[Utils, Components, API Routes]
    B --> E[Authentication Flow, Database Operations]
    C --> F[User Workflows, Critical Paths]

Test Framework Setup

Vitest Configuration

HKFR uses Vitest as the primary testing framework:

// vitest.config.js
export default defineConfig({
  plugins: [react({ jsxRuntime: 'automatic' })],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.js'],
    globals: true,
    css: false,
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'src/test/',
        '**/*.config.*'
      ]
    }
  }
});

Test Environment Setup

The test setup includes comprehensive mocking:

// src/test/setup.js
import '@testing-library/jest-dom';

// Mock Next.js components
vi.mock('next/navigation', () => ({
  useRouter: () => ({
    push: vi.fn(),
    replace: vi.fn(),
    back: vi.fn()
  }),
  usePathname: () => '/',
  redirect: vi.fn()
}));

// Mock authentication utilities
vi.mock('@/utils/auth', () => ({
  hashPassword: vi.fn(),
  comparePassword: vi.fn(),
  generateToken: vi.fn(),
  verifyToken: vi.fn()
}));

Testing Commands

Basic Commands

# Run tests in watch mode (development)
npm run test

# Run all tests once (CI/CD)
npm run test:run

# Run tests with coverage report
npm run test:coverage

# Run specific test file
npm run test -- auth.test.js

# Run tests matching pattern
npm run test -- --grep "authentication"

# Run tests with verbose output
npm run test -- --verbose

Advanced Commands

# Run tests in specific directory
npm run test -- src/app/api/

# Run tests with debugging
npm run test -- --inspect-brk

# Update snapshots
npm run test -- --update-snapshots

# Run tests in parallel
npm run test -- --threads

Test Structure

Directory Organization

hkfr/src/
├── app/
│   ├── __tests__/           # Page component tests
│   │   └── page.test.jsx
│   ├── api/
│   │   └── auth/
│   │       └── login/
│   │           └── __tests__/
│   │               └── route.test.js
│   └── ui/
│       └── __tests__/       # UI component tests
│           └── button.test.jsx
├── utils/
│   └── __tests__/           # Utility function tests
│       ├── auth.test.js
│       └── db.test.js
└── test/                    # Test utilities and setup
    ├── setup.js             # Global test setup
    ├── utils.js             # Test utilities
    ├── test-helpers.js      # Advanced helpers
    └── integration/         # Integration tests
        └── auth-flow.test.jsx

Unit Testing

Component Testing

Testing React components with React Testing Library:

// src/app/ui/__tests__/button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '../button';

describe('Button Component', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('handles click events', () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('applies size variants correctly', () => {
    render(<Button size="lg">Large button</Button>);
    const button = screen.getByText('Large button');
    expect(button).toHaveClass('text-lg'); // Tailwind class
  });
});

API Route Testing

Testing Next.js API routes:

// src/app/api/auth/login/__tests__/route.test.js
import { POST } from '../route';
import { NextRequest } from 'next/server';

describe('/api/auth/login', () => {
  it('should login successfully with valid credentials', async () => {
    const request = new NextRequest('http://localhost:3000/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({
        email: 'test@example.com',
        password: 'password123'
      })
    });

    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(200);
    expect(data.message).toBe('Login successful');
  });

  it('should return error for invalid credentials', async () => {
    const request = new NextRequest('http://localhost:3000/api/auth/login', {
      method: 'POST',
      body: JSON.stringify({
        email: 'test@example.com',
        password: 'wrongpassword'
      })
    });

    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(401);
    expect(data.error).toBe('Password is invalid');
  });
});

Utility Function Testing

Testing pure functions and utilities:

// src/utils/__tests__/auth.test.js
import { hashPassword, comparePassword, generateToken } from '../auth';

describe('Authentication Utilities', () => {
  describe('hashPassword', () => {
    it('should hash password correctly', () => {
      const password = 'testpassword';
      const hashed = hashPassword(password);

      expect(hashed).toBeDefined();
      expect(hashed).not.toBe(password);
      expect(hashed.length).toBeGreaterThan(20);
    });
  });

  describe('comparePassword', () => {
    it('should return true for matching passwords', () => {
      const password = 'testpassword';
      const hashed = hashPassword(password);

      expect(comparePassword(password, hashed)).toBe(true);
    });

    it('should return false for non-matching passwords', () => {
      const password = 'testpassword';
      const hashed = hashPassword(password);

      expect(comparePassword('wrongpassword', hashed)).toBe(false);
    });
  });

  describe('generateToken', () => {
    it('should generate valid JWT token', () => {
      const payload = { userId: '123', email: 'test@example.com' };
      const token = generateToken(payload);

      expect(token).toBeDefined();
      expect(typeof token).toBe('string');
      expect(token.split('.')).toHaveLength(3); // JWT structure
    });
  });
});

Integration Testing

Authentication Flow Testing

Testing complete authentication workflows:

// src/test/integration/auth-flow.test.jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { LoginPage } from '@/app/ui/login/page';

describe('Authentication Flow Integration', () => {
  it('should complete full login flow', async () => {
    // Mock API responses
    global.fetch = vi.fn()
      .mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({ message: 'Login successful' })
      });

    render(<LoginPage />);

    // Fill login form
    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' }
    });
    fireEvent.change(screen.getByLabelText(/password/i), {
      target: { value: 'password123' }
    });

    // Submit form
    fireEvent.click(screen.getByRole('button', { name: /login/i }));

    // Wait for success
    await waitFor(() => {
      expect(screen.getByText(/login successful/i)).toBeInTheDocument();
    });

    // Verify API call
    expect(global.fetch).toHaveBeenCalledWith('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'test@example.com',
        password: 'password123'
      })
    });
  });
});

Database Integration Testing

Testing database operations:

// src/utils/__tests__/db.test.js
import { database } from '../db';

describe('Database Integration', () => {
  beforeEach(() => {
    // Setup test database state
    vi.mocked(database.collection).mockReturnValue({
      findOne: vi.fn(),
      insertOne: vi.fn(),
      updateOne: vi.fn(),
      deleteOne: vi.fn()
    });
  });

  it('should connect to database successfully', () => {
    expect(database).toBeDefined();
    expect(typeof database.collection).toBe('function');
  });

  it('should handle database operations', async () => {
    const mockCollection = database.collection('users');
    mockCollection.findOne.mockResolvedValue({ 
      _id: '123', 
      email: 'test@example.com' 
    });

    const user = await mockCollection.findOne({ email: 'test@example.com' });
    expect(user).toEqual({ _id: '123', email: 'test@example.com' });
  });
});

Mocking Strategies

Next.js Component Mocking

// Mock Next.js Image component
vi.mock('next/image', () => ({
  default: ({ src, alt, ...props }) => (
    <img src={src} alt={alt} {...props} />
  )
}));

// Mock Next.js Link component
vi.mock('next/link', () => ({
  default: ({ children, href, ...props }) => (
    <a href={href} {...props}>{children}</a>
  )
}));

Database Mocking

// Mock MongoDB operations
vi.mock('@/utils/db', () => ({
  database: {
    collection: vi.fn(() => ({
      findOne: vi.fn(),
      find: vi.fn(() => ({
        toArray: vi.fn()
      })),
      insertOne: vi.fn(),
      updateOne: vi.fn(),
      deleteOne: vi.fn()
    }))
  }
}));

External Service Mocking

// Mock Google Cloud Storage
vi.mock('@/utils/gcs', () => ({
  storage: {
    bucket: vi.fn(() => ({
      file: vi.fn(() => ({
        createWriteStream: vi.fn(),
        createReadStream: vi.fn(),
        download: vi.fn()
      }))
    }))
  }
}));

Test Coverage

Current Coverage Status

  • Total Tests: 62
  • Passing Tests: 62 (100%)
  • Test Files: 7
  • Coverage: High coverage across all tested components

Coverage Report Structure

Coverage Summary:
├── Statements: 85%
├── Branches: 82%
├── Functions: 88%
└── Lines: 86%

Detailed Coverage:
├── src/utils/auth.js: 95%
├── src/app/ui/button.jsx: 100%
├── src/app/page.jsx: 90%
├── API Routes: 85%
└── Components: 88%

Generating Coverage Reports

# Generate HTML coverage report
npm run test:coverage

# View coverage report
open coverage/index.html

# Upload to Codecov (CI/CD)
npx codecov --token=$CODECOV_TOKEN

Testing Best Practices

Writing Effective Tests

  1. Descriptive Test Names:

    // Good
    it('should return error when email is missing from login request', () => {});
    
    // Bad
    it('should handle error', () => {});
    

  2. Arrange-Act-Assert Pattern:

    it('should hash password correctly', () => {
      // Arrange
      const password = 'testpassword';
    
      // Act
      const hashed = hashPassword(password);
    
      // Assert
      expect(hashed).not.toBe(password);
      expect(hashed.length).toBeGreaterThan(20);
    });
    

  3. Test Edge Cases:

    describe('validateEmail', () => {
      it('should accept valid email formats', () => {
        expect(validateEmail('user@example.com')).toBe(true);
      });
    
      it('should reject invalid email formats', () => {
        expect(validateEmail('invalid.email')).toBe(false);
        expect(validateEmail('')).toBe(false);
        expect(validateEmail(null)).toBe(false);
      });
    });
    

Component Testing Guidelines

  1. Test User Interactions:

    it('should toggle password visibility when eye icon is clicked', () => {
      render(<PasswordInput />);
      const toggleButton = screen.getByRole('button', { name: /toggle password/i });
      const passwordInput = screen.getByLabelText(/password/i);
    
      expect(passwordInput).toHaveAttribute('type', 'password');
    
      fireEvent.click(toggleButton);
      expect(passwordInput).toHaveAttribute('type', 'text');
    });
    

  2. Test Accessibility:

    it('should have proper ARIA labels', () => {
      render(<Button>Submit</Button>);
      const button = screen.getByRole('button');
    
      expect(button).toHaveAccessibleName('Submit');
      expect(button).not.toHaveAttribute('aria-disabled');
    });
    

API Testing Guidelines

  1. Test All HTTP Methods:

    describe('Document API', () => {
      it('should create document with POST', async () => {});
      it('should retrieve document with GET', async () => {});
      it('should update document with PUT', async () => {});
      it('should delete document with DELETE', async () => {});
    });
    

  2. Test Error Conditions:

    it('should return 401 for unauthenticated requests', async () => {
      const request = new NextRequest('http://localhost/api/documents');
      const response = await GET(request);
    
      expect(response.status).toBe(401);
    });
    

Continuous Integration

GitHub Actions Integration

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - run: cd hkfr && npm ci
      - run: cd hkfr && npm run test:run
      - run: cd hkfr && npm run test:coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3

Pre-commit Hooks

# Run tests before commit
npm run test:run

# Run linting
npm run lint

# Check build
npm run build

Debugging Tests

Common Debugging Techniques

  1. Use console.log in tests:

    it('should debug test data', () => {
      const result = processData(input);
      console.log('Debug result:', result);
      expect(result).toBeDefined();
    });
    

  2. Use debugger statements:

    it('should stop at breakpoint', () => {
      debugger; // Stops here when running with --inspect-brk
      expect(true).toBe(true);
    });
    

  3. Screen debugging for components:

    import { screen } from '@testing-library/react';
    
    it('should show current DOM', () => {
      render(<MyComponent />);
      screen.debug(); // Prints current DOM to console
    });
    

VSCode Testing Setup

// .vscode/launch.json
{
  "configurations": [
    {
      "name": "Debug Vitest Tests",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
      "args": ["--inspect-brk", "--no-coverage"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

Performance Testing

Load Testing API Endpoints

// Simple performance test
describe('API Performance', () => {
  it('should respond within acceptable time limits', async () => {
    const start = Date.now();

    const response = await fetch('/api/healthz');

    const duration = Date.now() - start;
    expect(response.status).toBe(200);
    expect(duration).toBeLessThan(1000); // Should respond within 1 second
  });
});

Memory Leak Testing

// Check for memory leaks in long-running operations
describe('Memory Usage', () => {
  it('should not leak memory during batch operations', () => {
    const initialMemory = process.memoryUsage().heapUsed;

    // Perform batch operations
    for (let i = 0; i < 1000; i++) {
      processLargeData();
    }

    // Force garbage collection
    global.gc && global.gc();

    const finalMemory = process.memoryUsage().heapUsed;
    const memoryIncrease = finalMemory - initialMemory;

    expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024); // Less than 10MB increase
  });
});

Future Testing Improvements

Planned Enhancements

  1. End-to-End Testing: Playwright or Cypress integration
  2. Visual Regression Testing: Screenshot comparison testing
  3. Contract Testing: API contract validation
  4. Property-Based Testing: Generative testing with fast-check
  5. Mutation Testing: Code quality validation with Stryker

Testing Infrastructure

  1. Parallel Testing: Faster test execution
  2. Test Sharding: Distribute tests across multiple runners
  3. Flaky Test Detection: Automatic retry and reporting
  4. Test Analytics: Test performance and reliability metrics