Prompt file imported from drewsephski/convext-workflow-orchestrator (
.windsurf/workflows/testing-standards.md). Copyright stays with the author.
Testing Standards Workflow
-
Unit Testing Setup Install testing dependencies:
bun add -D vitest @testing-library/react @testing-library/jest-dom jsdomConfigure Vitest (
vitest.config.ts):import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', setupFiles: ['./test/setup.ts'], }, }); -
Component Testing Create test file structure:
# Example component test touch components/users/__tests__/user-card.test.tsxComponent test template:
// components/users/__tests__/user-card.test.tsx import { render, screen } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { UserCard } from '../user-card'; describe('UserCard', () => { it('displays user information', () => { const mockUser = { _id: 'user123', name: 'John Doe', email: 'john@example.com', imageUrl: 'https://example.com/avatar.jpg', }; render(<UserCard userId="user123" />); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); it('shows loading state', () => { render(<UserCard userId="loading-user" />); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); }); -
Convex Testing Create Convex test file:
// convex/__tests__/users.test.ts import { convexTest } from 'convex-test'; import { describe, it, expect } from 'vitest'; import schema from '../schema'; import { api } from '../_generated/api'; describe('users', () => { it('creates a user correctly', async () => { const t = convexTest(schema); const userId = await t.mutation(api.users.create, { clerkId: 'user_123', email: 'test@example.com', name: 'Test User', }); const user = await t.query(api.users.get, { id: userId }); expect(user?.email).toBe('test@example.com'); }); it('validates email uniqueness', async () => { const t = convexTest(schema); await t.mutation(api.users.create, { clerkId: 'user_123', email: 'test@example.com', name: 'Test User', }); await expect( t.mutation(api.users.create, { clerkId: 'user_456', email: 'test@example.com', // Same email name: 'Another User', }) ).rejects.toThrow('User already exists'); }); }); -
E2E Testing Setup Configure Playwright:
bun add -D @playwright/testCreate E2E test structure:
mkdir -p e2e/auth e2e/dashboard e2e/profileE2E test template:
// e2e/auth/sign-in.spec.ts import { test, expect } from '@playwright/test'; test('sign-in flow', async ({ page }) => { await page.goto('/sign-in'); // Fill sign-in form await page.fill('[name="email"]', 'test@example.com'); await page.fill('[name="password"]', 'password123'); await page.click('button[type="submit"]'); // Verify redirect to dashboard await expect(page).toHaveURL('/dashboard'); await expect(page.locator('h1')).toContainText('Welcome'); }); -
Test Configuration Update
package.jsonscripts:{ "scripts": { "test": "vitest", "test:watch": "vitest --watch", "test:ui": "vitest --ui", "test:e2e": "playwright test", "test:e2e:headed": "playwright test --headed", "test:coverage": "vitest --coverage" } } -
CI Testing Pipeline Add to
.github/workflows/ci.yml:name: CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: latest - run: bun install --frozen-lockfile - run: bun run lint - run: bun run test - run: bun run test:e2e
Testing Patterns
Test Structure
__tests__/
├── unit/ # Unit tests
│ ├── lib/
│ └── components/
├── integration/ # Integration tests
│ ├── auth/
│ └── api/
├── e2e/ # End-to-end tests
│ ├── auth/
│ ├── dashboard/
│ └── profile/
└── fixtures/ # Test data
├── users.json
└── posts.json
Coverage Targets
- Unit Tests: 80% line coverage
- Integration Tests: 70% line coverage
- E2E Tests: 100% critical path coverage
Test Data Management
// test/fixtures/users.ts
export const mockUsers = [
{
_id: 'user1',
clerkId: 'clerk_1',
email: 'user1@example.com',
name: 'User One',
tier: 'free',
createdAt: Date.now(),
},
{
_id: 'user2',
clerkId: 'clerk_2',
email: 'user2@example.com',
name: 'User Two',
tier: 'pro',
createdAt: Date.now(),
},
];
export const createMockUser = (overrides = {}) => ({
_id: `user_${Date.now()}`,
clerkId: `clerk_${Date.now()}`,
email: 'test@example.com',
name: 'Test User',
tier: 'free',
createdAt: Date.now(),
...overrides,
});
Best Practices
-
Arrange-Act-Assert Pattern
it('should update user', async () => { // Arrange const initialUser = await createUser(); const updateData = { name: 'Updated Name' }; // Act await updateUser(initialUser._id, updateData); // Assert const updatedUser = await getUser(initialUser._id); expect(updatedUser.name).toBe('Updated Name'); }); -
Test Isolation
- Each test is independent
- Clean up database state
- Use fresh test instances
-
Mock External Services
// Mock Clerk in tests vi.mock('@clerk/nextjs/react', () => ({ useAuth: () => ({ userId: 'test-user-id', isSignedIn: true, }), })); // Mock Convex in tests vi.mock('convex/react', () => ({ useQuery: vi.fn(), useMutation: vi.fn(), }));
