Testing Guide
This project uses Vitest for all testing (API and frontend).
Quick Start
# Run all tests
pnpm test
# Run API tests only
pnpm test:api
# Run web tests only
pnpm test:web
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Open Vitest UI
pnpm test:ui
Test Structure
API Tests (apps/api)
Location: src/**/*.spec.ts
Example:
import { describe, it, expect } from 'vitest';
import { MyService } from './my.service';
describe('MyService', () => {
it('should do something', () => {
expect(MyService).toBeDefined();
});
});
Web Tests (apps/web)
Location: src/**/*.test.{ts,tsx}
Example:
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
});
Testing Stack
- Test Runner: Vitest
- React Testing: @testing-library/react
- User Interactions: @testing-library/user-event
- Assertions: @testing-library/jest-dom (matchers)
Best Practices
1. Test Organization
Group related tests using describe blocks:
describe('MyComponent', () => {
describe('when logged in', () => {
it('shows user menu', () => {});
});
describe('when logged out', () => {
it('shows login button', () => {});
});
});
2. Use Descriptive Test Names
// ❌ BAD
it('works', () => {});
// ✅ GOOD
it('should display error message when form is invalid', () => {});
3. Arrange, Act, Assert
it('should add two numbers', () => {
// Arrange
const a = 1;
const b = 2;
// Act
const result = add(a, b);
// Assert
expect(result).toBe(3);
});
4. Mock External Dependencies
import { vi } from 'vitest';
const mockFetch = vi.fn();
global.fetch = mockFetch;
it('fetches data', async () => {
mockFetch.mockResolvedValue({ json: () => ({ data: 'test' }) });
// Test implementation
});
5. Test User Behavior, Not Implementation
// ❌ BAD - testing implementation
expect(component.state.isOpen).toBe(true);
// ✅ GOOD - testing behavior
expect(screen.getByRole('dialog')).toBeVisible();
CI/CD Integration
Tests run automatically on:
- Push to
master,main, ordevelopbranches - Pull requests to these branches
CI Pipeline
- Unit tests – Fast job, no services required
- E2E tests – Uses Postgres 16 and Redis 7 services; requires
JWT_SECRETand OAuth env vars - Coverage – Runs after both pass; uploads to Codecov
See .github/workflows/test.yml for configuration.
Coverage
Coverage reports are generated in:
apps/api/coverage/apps/web/coverage/
View HTML coverage reports:
# API
open apps/api/coverage/index.html
# Web
open apps/web/coverage/index.html
Common Testing Patterns
Testing React Components
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
it('handles user interaction', async () => {
const user = userEvent.setup();
render(<Button onClick={mockFn}>Click me</Button>);
await user.click(screen.getByRole('button'));
expect(mockFn).toHaveBeenCalled();
});
Testing Async Code
it('fetches data', async () => {
const promise = fetchData();
expect(promise).toBeInstanceOf(Promise);
const data = await promise;
expect(data).toEqual({ id: 1 });
});
Testing Errors
it('throws error for invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
Troubleshooting
Tests Run Slowly
- Use
test.concurrentfor independent tests - Mock expensive operations
- Reduce test data size
Flaky Tests
- Avoid hardcoded timeouts
- Use
waitForfor async operations - Mock date/time functions
Import Errors
- Check
vitest.config.tsfor path aliases - Ensure dependencies are installed
- Verify TypeScript configuration