Testing Guide
This project uses Vitest for all testing (API and frontend).
Use Node 24 locally (repo root .nvmrc). With nvm: nvm install and nvm use before running tests so your version matches CI.
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
- Coverage – Runs after unit tests pass; uploads to Codecov
There is currently no API e2e job. The previous suite ran on Jest, which cannot load TypeScript 7, so it was removed when the workspace unified on TS7. When e2e coverage is rebuilt on Vitest it will run as a non-gating job so it never blocks merges or deploys.
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