9.2 KiB
Testing Guide
This document provides comprehensive information about the testing infrastructure for the Discord Spywatcher project.
Table of Contents
- Overview
- Backend Testing
- Frontend Testing
- End-to-End Testing
- Running Tests
- Writing Tests
- Test Coverage
- CI/CD Integration
Overview
The Discord Spywatcher project uses a comprehensive testing strategy including:
- Unit Tests: Test individual functions and components in isolation
- Integration Tests: Test API endpoints and component interactions
- End-to-End Tests: Test complete user workflows
Testing Stack
Backend:
- Jest - Test framework
- ts-jest - TypeScript support for Jest
- Supertest - HTTP assertion library
Frontend:
- Vitest - Fast unit test framework
- React Testing Library - React component testing
- Playwright - End-to-end testing
Backend Testing
Setup
The backend uses Jest with TypeScript support. Configuration is in backend/jest.config.js.
Test Structure
backend/
__tests__/
unit/
analytics/ # Analytics function tests
middleware/ # Middleware tests
utils/ # Utility function tests
integration/
routes/ # API endpoint tests
auth/ # Authentication flow tests
e2e/ # End-to-end tests
__mocks__/ # Mock files (Discord API, etc.)
setup.ts # Test configuration and globals
Running Backend Tests
cd backend
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Run only unit tests
npm run test:unit
# Run only integration tests
npm run test:integration
# Run only E2E tests
npm run test:e2e
Backend Test Examples
Unit Test (Analytics Function)
import { getGhostScores } from '../../../src/analytics/ghosts';
import { db } from '../../../src/db';
jest.mock('../../../src/db');
describe('Analytics - Ghost Scores', () => {
it('should calculate ghost scores correctly', async () => {
const mockTypings = [
/* mock data */
];
const mockMessages = [
/* mock data */
];
(db.typingEvent.groupBy as jest.Mock).mockResolvedValue(mockTypings);
(db.messageEvent.groupBy as jest.Mock).mockResolvedValue(mockMessages);
const result = await getGhostScores('test-guild-id');
expect(result).toHaveLength(2);
expect(result[0].ghostScore).toBeCloseTo(3.33);
});
});
Integration Test (API Endpoint)
import request from 'supertest';
import app from '../../../src/server';
describe('GET /api/analytics/ghosts', () => {
it('should return ghost scores', async () => {
const response = await request(app)
.get('/api/analytics/ghosts')
.set('Authorization', `Bearer ${validToken}`)
.expect(200);
expect(response.body).toBeInstanceOf(Array);
});
});
Mocking
Discord API Mock
Located in __tests__/__mocks__/discord.ts:
export const mockDiscordUser = {
id: '123456789',
username: 'testuser',
email: 'test@example.com',
// ...
};
Frontend Testing
Setup
The frontend uses Vitest with React Testing Library. Configuration is in frontend/vite.config.ts.
Test Structure
frontend/
src/
__tests__/
components/ # React component tests
hooks/ # Custom hook tests
pages/ # Page component tests
store/ # State management tests
lib/ # API client tests
integration/ # Integration tests
__mocks__/ # Mock data and modules
setup.ts # Test configuration
e2e/ # Playwright E2E tests
specs/ # E2E test specifications
Running Frontend Tests
cd frontend
# Run all unit/integration tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with UI
npm run test:ui
# Run tests with coverage
npm run test:coverage
# Run E2E tests
npm run test:e2e
# Run E2E tests with UI
npm run test:e2e:ui
# Debug E2E tests
npm run test:e2e:debug
Frontend Test Examples
Component Test
import { render, screen } from '@testing-library/react';
import SessionStatus from '../../components/SessionStatus';
describe('SessionStatus Component', () => {
it('should display user info', () => {
render(<SessionStatus />);
expect(screen.getByText('TestUser')).toBeInTheDocument();
});
});
Hook Test
import { renderHook, waitFor } from '@testing-library/react';
import { useSession } from '../../hooks/useSession';
describe('useSession hook', () => {
it('should fetch session data', async () => {
const { result } = renderHook(() => useSession());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.session).toBeDefined();
});
});
Store Test
import { useAuth } from '../../store/auth';
describe('Auth Store', () => {
it('should set access token', () => {
useAuth.getState().setToken('test-token');
expect(useAuth.getState().accessToken).toBe('test-token');
});
});
End-to-End Testing
Setup
E2E tests use Playwright. Configuration is in frontend/playwright.config.ts.
Running E2E Tests
cd frontend
# Run E2E tests (headless)
npm run test:e2e
# Run E2E tests with UI
npm run test:e2e:ui
# Debug E2E tests
npm run test:e2e:debug
E2E Test Example
import { test, expect } from '@playwright/test';
test('should login with Discord', async ({ page }) => {
await page.goto('/');
await page.click('text=Login with Discord');
// ... test authentication flow
await expect(page).toHaveURL('/dashboard');
});
Test Coverage
Coverage Goals
- Backend: >80% code coverage
- Frontend: >70% code coverage
- Critical Paths: 100% coverage
Viewing Coverage Reports
# Backend
cd backend
npm run test:coverage
# Open backend/coverage/index.html in browser
# Frontend
cd frontend
npm run test:coverage
# Open frontend/coverage/index.html in browser
Coverage Configuration
Coverage thresholds are configured in:
- Backend:
jest.config.js - Frontend:
vite.config.ts
Writing Tests
Best Practices
-
Test Behavior, Not Implementation
- Focus on what the code does, not how it does it
- Test user-facing behavior and API contracts
-
Keep Tests Simple and Focused
- One test should test one thing
- Use descriptive test names
-
Use Proper Mocking
- Mock external dependencies (API calls, database)
- Don't mock the code you're testing
-
Test Edge Cases
- Empty arrays/objects
- Null/undefined values
- Error conditions
- Boundary values
-
Maintain Test Data
- Use factories or fixtures for test data
- Keep test data minimal but realistic
Test Naming Convention
describe('Component/Function Name', () => {
describe('Method or Feature', () => {
it('should [expected behavior] when [condition]', () => {
// Test implementation
});
});
});
Test Organization
describe('Component', () => {
// Setup
beforeEach(() => {
// Reset state, clear mocks
});
// Happy path tests
it('should work in normal conditions', () => {});
// Edge cases
it('should handle empty data', () => {});
it('should handle errors', () => {});
// Cleanup
afterEach(() => {
// Cleanup if needed
});
});
CI/CD Integration
GitHub Actions
Tests run automatically on:
- Pull requests
- Pushes to main branch
- Manual workflow dispatch
Required Checks
Before merging:
- All unit tests must pass
- All integration tests must pass
- Code coverage must meet thresholds
- E2E tests for critical paths must pass
Local Pre-commit Testing
# Run all tests before committing
cd backend && npm test && cd ../frontend && npm test
Troubleshooting
Common Issues
Tests Timeout
// Increase timeout for slow tests
jest.setTimeout(10000); // Backend
test.setTimeout(10000); // Frontend
Mock Issues
// Clear mocks between tests
beforeEach(() => {
jest.clearAllMocks();
});
Environment Variables
Tests use environment variables from __tests__/setup.ts. Add test-specific env vars there.
Additional Resources
- Jest Documentation
- Vitest Documentation
- React Testing Library
- Playwright Documentation
- Testing Best Practices
Contributing
When adding new features:
- Write tests first (TDD approach)
- Ensure tests cover edge cases
- Update this guide if adding new test patterns
- Verify coverage meets thresholds
For questions or issues with tests, please open an issue on GitHub.