Testing Guide
OpenCodeHub uses Vitest for unit and integration testing, and Playwright for end-to-end testing.
Test Suite Overview
| Suite | Framework | Location | Count |
|---|---|---|---|
| Unit Tests | Vitest | tests/unit/ |
300+ |
| Integration Tests | Vitest | tests/integration/ |
100+ |
| Security Tests | Vitest | tests/security.test.ts |
20+ |
| E2E Tests | Playwright | tests/e2e/ |
50+ |
| Load Tests | Custom | tests/load/ |
5+ |
Total: 546 tests across 114 test files (100% passing)
Running Tests
All Tests
npm test
# or
bun test
Unit Tests Only
npm test -- --run tests/unit
Integration Tests Only
npm test -- --run tests/integration
Specific Test File
npm test -- --run tests/unit/auth.test.ts
Watch Mode
npm test -- --watch
With Coverage
npm run test:coverage
E2E Tests
npx playwright test
Run specific E2E test:
npx playwright test tests/e2e/login.spec.ts
Run with UI mode:
npx playwright test --ui
Test Structure
Unit Tests
Test individual functions and modules in isolation:
// tests/unit/example.test.ts
import { describe, it, expect } from "vitest";
import { myFunction } from "@/lib/example";
describe("myFunction", () => {
it("returns correct result for valid input", () => {
expect(myFunction("valid")).toBe("expected");
});
it("throws on invalid input", () => {
expect(() => myFunction("invalid")).toThrow();
});
});
Integration Tests
Test API routes with mocked database and services:
// tests/integration/example-route.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
const mocks = vi.hoisted(() => ({
getUserFromRequestMock: vi.fn().mockResolvedValue({
userId: "usr_1",
username: "alice",
}),
}));
vi.mock("@/lib/auth", () => ({
getUserFromRequest: mocks.getUserFromRequestMock,
}));
// ... mock database, etc.
describe("GET /api/example", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.getUserFromRequestMock.mockResolvedValue({
userId: "usr_1",
username: "alice",
});
});
it("returns 200 for authenticated user", async () => {
// Test implementation
});
});
Mock Best Practices
- Use
vi.hoisted()for mock definitions that must be set up before imports - Use
vi.clearAllMocks()inbeforeEachto clear call history - Reset mock implementations in
beforeEachif tests change them:beforeEach(() => { vi.clearAllMocks(); mocks.myMock.mockResolvedValue(defaultValue); }); - The
success()helper wraps API responses as{ success: true, data: ... }- Access response data via
json.data.*notjson.*
- Access response data via
Writing New Tests
1. Create the Test File
Place tests alongside the code or in tests/:
# Unit test for src/lib/new-feature.ts
touch tests/unit/new-feature.test.ts
# Integration test for src/pages/api/new-route.ts
touch tests/integration/new-route.test.ts
2. Mock Dependencies
Mock external modules using vi.mock():
vi.mock("@/db", () => ({
getDatabase: () => mockDb,
schema: mockSchema,
}));
3. Write Test Cases
Cover:
- Happy path (normal operation)
- Authentication failures (401)
- Authorization failures (403)
- Not found (404)
- Validation errors (400)
- Edge cases (empty arrays, null values)
4. Run and Verify
npm test -- --run tests/unit/new-feature.test.ts
E2E Testing with Playwright
Setup
Playwright is configured in playwright.config.ts.
Writing E2E Tests
// tests/e2e/login.spec.ts
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("/login");
await page.fill('input[name="username"]', "testuser");
await page.fill('input[name="password"]', "password");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
});
Accessibility Testing
npx playwright test --project=accessibility
Uses @axe-core/playwright to check for accessibility violations.
Test Commands Reference
| Command | Description |
|---|---|
npm test |
Run all tests |
npm test -- --run |
Run once (no watch) |
npm test -- --watch |
Watch mode |
npm test -- --run <pattern> |
Run matching files |
npm run test:coverage |
Run with coverage report |
npx playwright test |
Run E2E tests |
npx playwright test --ui |
Run E2E with UI |
npx playwright test --headed |
Run E2E in visible browser |
npm run lint |
Run ESLint |
npm run typecheck |
Run TypeScript check |
CI Integration
Tests run automatically in the CI pipeline:
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --run
- run: npx playwright test
Troubleshooting
"Module not found" in tests
Ensure the path alias is correct. Use @/ prefix for src files:
import { something } from "@/lib/something"; // Correct
import { something } from "../../lib/something"; // Avoid
"Cannot read properties of undefined" in integration tests
Usually caused by mock drift:
- Check if
json.data.*is used for routes returningsuccess() - Ensure mock implementations are reset in
beforeEach - Verify all mocked modules are properly hoisted
Slow tests
- Use
vi.useFakeTimers()for time-dependent code - Mock heavy operations (database queries, file I/O)
- Use
--runflag instead of watch mode in CI