| title | RevealUI Testing Patterns |
| visibility | internal |
| status | verified |
| audience | agent |
| name | revealui-testing-patterns |
| description | Testing patterns and best practices for RevealUI with Vitest |
| version | 0.1.0 |
| author | RevealUI Team |
| tags | ["testing","vitest","quality"] |
| compatibility | ["claude-code","universal"] |
| allowedTools | ["Read","Write","Edit","Bash"] |
RevealUI Testing Patterns
Best practices for testing RevealUI applications with Vitest, addressing cyclic dependencies and test infrastructure issues.
Current Status
Challenge: Test infrastructure exists but has blockers
- Cyclic dependency issues preventing test execution
- Need for per-worker test isolation
- PGlite database initialization for tests
Test Structure
File Organization
packages/
└── package-name/
├── src/
│ ├── index.ts
│ └── __tests__/
│ ├── unit.test.ts
│ └── integration.test.ts
└── vitest.config.ts
apps/
└── admin/
└── src/
├── __tests__/
│ ├── auth/
│ ├── integration/
│ └── setup.ts
└── vitest.config.ts
Test Setup File
import { beforeAll, afterAll, beforeEach } from 'vitest'
import { initializeTestDatabase } from './helpers/database'
beforeAll(async () => {
await initializeTestDatabase()
}, 30000)
afterAll(async () => {
})
beforeEach(() => {
})
Unit Testing Patterns
Test Components
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Button } from './Button'
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', async () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click</Button>)
await userEvent.click(screen.getByText('Click'))
expect(handleClick).toHaveBeenCalledOnce()
})
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})
Test Utilities
import { describe, it, expect } from 'vitest'
import { formatDate, validateEmail } from './utils'
describe('formatDate', () => {
it('formats date correctly', () => {
const date = new Date('2024-01-01')
expect(formatDate(date)).toBe('January 1, 2024')
})
it('handles invalid dates', () => {
expect(() => formatDate(null)).toThrow('Invalid date')
})
})
describe('validateEmail', () => {
it('validates correct emails', () => {
expect(validateEmail('user@example.com')).toBe(true)
})
it('rejects invalid emails', () => {
expect(validateEmail('invalid')).toBe(false)
expect(validateEmail('user@')).toBe(false)
})
})
Integration Testing
API Routes
import { describe, it, expect, beforeEach } from 'vitest'
import { createMockRequest, createMockResponse } from './helpers'
describe('POST /api/posts', () => {
beforeEach(async () => {
await clearDatabase()
})
it('creates a new post', async () => {
const req = createMockRequest({
method: 'POST',
body: {
title: 'Test Post',
content: 'Test content',
},
})
const res = createMockResponse()
await handler(req, res)
expect(res.status).toHaveBeenCalledWith(201)
expect(res.json).toHaveBeenCalledWith(
expect.objectContaining({
id: expect.any(String),
title: 'Test Post',
})
)
})
it('requires authentication', async () => {
const req = createMockRequest({
method: 'POST',
headers: {},
})
const res = createMockResponse()
await handler(req, res)
expect(res.status).toHaveBeenCalledWith(401)
})
})
Database Operations
import { describe, it, expect, beforeEach } from 'vitest'
import { getRevealUI } from '@revealui/core'
import { buildConfig } from '@revealui/core'
describe('Posts Collection', () => {
let revealui: Awaited<ReturnType<typeof getRevealUI>>
beforeEach(async () => {
const config = await buildConfig({ })
revealui = await getRevealUI({ config })
await revealui.delete({ collection: 'posts', where: {} })
})
it('creates a post', async () => {
const post = await revealui.create({
collection: 'posts',
data: {
title: 'Test Post',
_status: 'published',
},
})
expect(post).toMatchObject({
id: expect.any(String),
title: 'Test Post',
_status: 'published',
})
})
it('finds posts by status', async () => {
await revealui.create({
collection: 'posts',
data: { title: 'Published', _status: 'published' },
})
await revealui.create({
collection: 'posts',
data: { title: 'Draft', _status: 'draft' },
})
const { docs } = await revealui.find({
collection: 'posts',
where: { _status: { equals: 'published' } },
})
expect(docs).toHaveLength(1)
expect(docs[0].title).toBe('Published')
})
})
Mocking Patterns
Mock External Services
import { vi, beforeEach } from 'vitest'
global.fetch = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
it('fetches user data', async () => {
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({ id: '1', name: 'John' }),
})
const user = await fetchUser('1')
expect(fetch).toHaveBeenCalledWith('/api/users/1')
expect(user).toEqual({ id: '1', name: 'John' })
})
Mock Modules
vi.mock('@revealui/auth', () => ({
getCurrentUser: vi.fn(),
checkPermission: vi.fn(),
}))
import { getCurrentUser } from '@revealui/auth'
it('requires authentication', async () => {
(getCurrentUser as any).mockResolvedValue(null)
const result = await protectedAction()
expect(result.error).toBe('Unauthorized')
})
Vitest Configuration
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/__tests__/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'dist/',
'**/*.config.{js,ts}',
'**/__tests__/**',
],
},
pool: 'threads',
poolOptions: {
threads: {
singleThread: false,
},
},
},
})
Solving Common Issues
Cyclic Dependencies
import { funcB } from './fileB'
export const funcA = () => funcB()
import { funcA } from './fileA'
export const funcB = () => funcA()
export const funcA = (callback: () => void) => callback()
export const funcB = () => console.log('B')
import { funcA } from './fileA'
import { funcB } from './fileB'
funcA(funcB)
Async Test Timeouts
it('initializes database', async () => {
await initializeDatabase()
}, 30000)
it('handles multiple async operations', (done) => {
Promise.all([
operation1(),
operation2(),
operation3(),
]).then(() => {
expect(true).toBe(true)
done()
})
})
Database Isolation
import { afterAll, beforeAll } from 'vitest'
let db: Database
beforeAll(async () => {
const workerId = process.env.VITEST_WORKER_ID || '1'
db = await createTestDatabase(`test-db-${workerId}`)
})
afterAll(async () => {
await db.destroy()
})
Test Coverage Goals
Target: 80%+ coverage
Current: Infrastructure in development
Run coverage:
pnpm test:coverage
Best Practices
1. Test Behavior, Not Implementation
it('sets state correctly', () => {
const { result } = renderHook(() => useState(0))
expect(result.current[0]).toBe(0)
})
it('displays initial count', () => {
render(<Counter />)
expect(screen.getByText('Count: 0')).toBeInTheDocument()
})
2. Use Descriptive Test Names
it('works', () => { })
it('creates a post with valid data', () => { })
it('returns 401 when user is not authenticated', () => { })
3. Arrange, Act, Assert
it('updates user profile', async () => {
const user = await createTestUser()
const updates = { name: 'New Name' }
const result = await updateUser(user.id, updates)
expect(result.name).toBe('New Name')
})
4. One Assertion Focus Per Test
it('validates required fields', () => {
expect(validate({})).toHaveProperty('email')
})
it('validates email format', () => {
expect(validate({ email: 'invalid' })).toHaveProperty('email')
})
Running Tests
pnpm test
pnpm test:watch
pnpm test:coverage
pnpm test path/to/file.test.ts
pnpm --filter @revealui/core test
Debugging Tests
it('debugs test', () => {
const mock = vi.fn()
mock('test')
console.log(mock.mock.calls)
})
Resources
Every test written makes RevealUI more reliable! 🧪