Development
Testing
How to write and run tests with Vitest and React Testing Library.
Testing Approach
Haze Dashboard uses Vitest for unit and component testing, combined with React Testing Library for rendering and interacting with components. Vitest is Vite-native, so it shares the same config and plugin pipeline as your dev server — tests run fast and resolve path aliases automatically.
Tests live in a dedicated tests/ directory or alongside the code they test. Use the .test.ts or .test.tsx file extension.
Setup
Install the testing dependencies:
npm install -D vitest @testing-library/react @testing-library/jest-dom happy-dom @vitejs/plugin-reactCreate a vitest.config.ts at the project root:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
test: {
environment: 'happy-dom',
globals: true,
include: ['tests/**/*.test.ts?(x)', 'src/**/*.test.ts?(x)'],
},
resolve: {
alias: { '@': resolve(__dirname, 'src') },
},
})Writing a Component Test
Test that a component renders correctly with given props and responds to user interaction:
// tests/components/status-badge.test.tsx
import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { StatusBadge } from '@/components/shared/status-badge'
describe('StatusBadge', () => {
it('renders the status text', () => {
const { getByText } = render(<StatusBadge status="active" />)
expect(getByText(/active/i)).toBeDefined()
})
it('applies success color for completed status', () => {
const { container } = render(<StatusBadge status="completed" />)
expect(container.innerHTML).toMatch(/green|success/)
})
it('applies error color for cancelled status', () => {
const { container } = render(<StatusBadge status="cancelled" />)
expect(container.innerHTML).toMatch(/red|error/)
})
})Writing a Page Test
Page tests verify that a page component renders its main elements:
// tests/pages/dashboard.test.tsx
import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
import DashboardPage from '@/app/(dashboard)/dashboard/page'
describe('Dashboard Page', () => {
it('renders the page heading', () => {
const { container } = render(<DashboardPage />)
expect(container.innerHTML).toBeTruthy()
})
})Testing Hooks
Test custom hooks with React Testing Library's renderHook and act:
// tests/hooks/use-theme-settings.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { renderHook, act } from '@testing-library/react'
import { useThemeSettings } from '@/hooks/use-theme-settings'
describe('useThemeSettings', () => {
beforeEach(() => {
localStorage.clear()
})
it('starts with default settings', () => {
const { result } = renderHook(() => useThemeSettings())
expect(result.current.accentColor).toBe('teal')
expect(result.current.density).toBe('default')
})
it('persists accent color changes', () => {
const { result } = renderHook(() => useThemeSettings())
act(() => result.current.setAccentColor('blue'))
expect(result.current.accentColor).toBe('blue')
})
})Running Tests
| Command | Description |
|---|---|
npx vitest | Run tests in watch mode (re-runs on file changes) |
npx vitest run | Run all tests once and exit (CI-friendly) |
npx vitest --ui | Open the interactive Vitest UI in a browser |
npx vitest --coverage | Generate coverage report (requires @vitest/coverage-v8) |
npx vitest run tests/components/ | Run tests in a specific directory |
Mocking API Routes
Components that use fetch() can be tested by mocking the global fetch:
// tests/components/orders-list.test.tsx
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, waitFor } from '@testing-library/react'
import OrdersList from '@/components/orders-list'
beforeEach(() => {
vi.stubGlobal('fetch', vi.fn(() =>
Promise.resolve({
json: () => Promise.resolve({
data: [
{ id: 1, orderNumber: 'ORD-001', status: 'active' },
{ id: 2, orderNumber: 'ORD-002', status: 'pending' },
],
meta: { total: 2, page: 1, perPage: 10, lastPage: 1 },
}),
} as Response)
))
})
describe('OrdersList', () => {
it('renders orders from the API', async () => {
const { getByText } = render(<OrdersList />)
await waitFor(() => expect(getByText('ORD-001')).toBeDefined())
})
})Tip
For pure functions (utilities, formatters, permission helpers), write plain Vitest tests without any rendering setup. They're the fastest tests you can write and the easiest to maintain.
Next Steps
Ready to go live? See the Deployment guide for instructions on deploying to Vercel, Netlify, Cloudflare Pages, or a Node.js server.