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¶
-
Descriptive Test Names:
-
Arrange-Act-Assert Pattern:
-
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¶
-
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'); }); -
Test Accessibility:
API Testing Guidelines¶
-
Test All HTTP Methods:
-
Test Error Conditions:
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¶
Debugging Tests¶
Common Debugging Techniques¶
-
Use console.log in tests:
-
Use debugger statements:
-
Screen debugging for components:
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¶
- End-to-End Testing: Playwright or Cypress integration
- Visual Regression Testing: Screenshot comparison testing
- Contract Testing: API contract validation
- Property-Based Testing: Generative testing with fast-check
- Mutation Testing: Code quality validation with Stryker
Testing Infrastructure¶
- Parallel Testing: Faster test execution
- Test Sharding: Distribute tests across multiple runners
- Flaky Test Detection: Automatic retry and reporting
- Test Analytics: Test performance and reliability metrics