Frontend Testing with Vitest
This document explains how to use the Vitest testing setup for the tracker-frontend and tracker-admin projects.
Overview
Both frontend projects are configured with:
- Vitest for unit testing
- @testing-library/react for React component testing
- Coverage reporting with v8 provider
- SonarQube integration via LCOV reports
Quick Start
Running Tests
Individual Projects
# Frontend project
cd tracker-frontend
npm run test # Run tests in watch mode
npm run test:run # Run tests once
npm run test:coverage # Run tests with coverage
npm run test:ui # Run tests with UI interface
# Admin project
cd tracker-admin
npm run test # Run tests in watch mode
npm run test:run # Run tests once
npm run test:coverage # Run tests with coverage
npm run test:ui # Run tests with UI interface
Both Projects at Once
# From root directory
./run_frontend_tests.sh
In Development Containers
# Frontend container
docker compose exec frontend-dev npm run test:coverage
# Admin container
docker compose exec admin-dev npm run test:coverage
Configuration
Vitest Configuration
- Config files:
vitest.config.tsin each project - Test environment: jsdom (for DOM APIs)
- Coverage provider: v8
- Coverage formats: text, json, html, lcov
Test Setup
- Setup files:
src/test/setup.tsin each project - Global mocks: DOM APIs, localStorage, Leaflet, react-leaflet
- Testing utilities: @testing-library/jest-dom matchers
Coverage Settings
- Thresholds: 80% for branches, functions, lines, statements
- Excluded files:
- node_modules/
- test files
- config files
- build/dist directories
- main.tsx and vite-env.d.ts
Writing Tests
Basic Test Structure
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { MyComponent } from './MyComponent'
describe('MyComponent', () => {
it('should render correctly', () => {
render(<MyComponent />)
expect(screen.getByText('Hello World')).toBeInTheDocument()
})
})
Testing React Components
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
describe('Button Component', () => {
it('should call onClick when clicked', async () => {
const user = userEvent.setup()
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledOnce()
})
})
Testing Utilities/Hooks
import { describe, it, expect } from "vitest";
import { renderHook } from "@testing-library/react";
import { useCustomHook } from "./useCustomHook";
describe("useCustomHook", () => {
it("should return expected value", () => {
const { result } = renderHook(() => useCustomHook());
expect(result.current.value).toBe("expected");
});
});
Available Mocks
Global Mocks (automatically available)
window.matchMediaResizeObserverIntersectionObserverlocalStorage/sessionStorageURL.createObjectURL/URL.revokeObjectURLfetch
Leaflet Mocks
leaflet- All Leaflet APIs are mockedreact-leaflet- All react-leaflet components are mocked
Custom Mocks
import { vi } from "vitest";
// Mock a module
vi.mock("./api/client", () => ({
fetchData: vi.fn(() => Promise.resolve({ data: "mocked" })),
}));
// Mock environment variables
vi.stubEnv("VITE_API_URL", "http://test-api.com");
Coverage Reports
Generated Files
- HTML Report:
coverage/index.html(viewable in browser) - LCOV Report:
coverage/lcov.info(for SonarQube) - JSON Report:
coverage/coverage-final.json - Text Report: Displayed in terminal
Viewing Coverage
# Generate and view HTML coverage report
npm run test:coverage
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
SonarQube Integration
Configuration
The sonar-project.properties file is configured to pick up frontend coverage:
sonar.javascript.lcov.reportPaths=tracker-frontend/coverage/lcov.info,tracker-admin/coverage/lcov.info
Workflow
- Run tests with coverage:
./run_frontend_tests.sh - Submit to SonarQube:
sonar-scanner - View results in SonarQube dashboard
VSCode Integration
Extensions
Install the Vitest extension for VSCode to get:
- Test discovery in the sidebar
- Inline test results
- Debug support
- Coverage visualization
Settings
The project includes Vitest support in .vscode/settings.json:
{
"vitest.enable": true
}
Troubleshooting
Common Issues
Tests not found
- Ensure test files end with
.test.tsor.test.tsx - Check that test files are in the
src/directory - Verify vitest.config.ts is properly configured
Coverage not generated
- Ensure you're using
npm run test:coverage - Check that the coverage directory has write permissions
- Verify v8 coverage provider is installed
Mock issues
- Check that mocks are defined before imports
- Use
vi.hoisted()for hoisted mocks if needed - Ensure mock paths match actual module paths
Container testing
- Ensure containers are running:
docker compose up -d - Check that node_modules are properly mounted
- Verify npm scripts work inside containers
Debug Mode
# Run tests in debug mode
npm run test -- --reporter=verbose
# Run specific test file
npm run test -- src/components/MyComponent.test.tsx
# Run tests matching pattern
npm run test -- --grep "should render"
Best Practices
Test Organization
- Group related tests with
describeblocks - Use descriptive test names
- Follow AAA pattern: Arrange, Act, Assert
Mocking Strategy
- Mock external dependencies
- Use real implementations for internal utilities
- Mock at the module boundary, not implementation details
Coverage Goals
- Aim for 80%+ coverage on critical paths
- Focus on business logic over UI components
- Test error conditions and edge cases
Performance
- Use
vi.fn()for simple mocks - Avoid unnecessary DOM rendering in unit tests
- Clean up after tests with
afterEachhooks
Examples
See the example test files:
tracker-frontend/src/test/example.test.tstracker-admin/src/test/example.test.ts
For more examples, refer to the Vitest documentation and Testing Library documentation.