Swadhin Biswas feat: production readiness — fix critical bugs, achieve 100% test pass rate, complete documentation · 4/30/2026
ac8f8cc
298 lines · 6.0 KB
Raw Download

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() in beforeEach to clear call history
  • Reset mock implementations in beforeEach if 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.* not json.*

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:

  1. Check if json.data.* is used for routes returning success()
  2. Ensure mock implementations are reset in beforeEach
  3. 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 --run flag instead of watch mode in CI

See Also