Testing
Technical reference for the helldivers.bot testing infrastructure. Audience: project owner and AI assistants.
Overview
The project uses Vitest for all tests, with two separate configs:
- Unit tests (
vitest.config.mjs) — utilities, validators, API route handlers, queries, features, and shared modules - Smoke tests (
vitest.smoke.config.mjs) — HTTP-level checks against a running dev server
Tests live in src/__tests__/ following conventions from the euraika/aegis projects.
graph TD
subgraph Unit["VITEST UNIT TESTS"]
UTILS["src/__tests__/unit/utils/"]
VALS["src/__tests__/unit/validators/"]
SHARED["src/__tests__/unit/shared/"]
QUERIES["src/__tests__/unit/queries/"]
ROUTES["src/__tests__/unit/routes/"]
FEATURES["src/__tests__/unit/features/"]
DB["src/__tests__/unit/db/"]
UPDATE["src/__tests__/unit/update/"]
APP["src/__tests__/unit/app/"]
end
subgraph Smoke["VITEST SMOKE TESTS"]
SMOKETEST["smoke.test.mjs<br/><small>Page loads + API checks</small>"]
end
subgraph Mocks["GLOBAL MOCKS (vitest.setup.mjs)"]
AUTH_M["BetterAuth mock<br/><small>null session default</small>"]
PRISMA_M["Prisma mock<br/><small>All model CRUD stubs</small>"]
NEXT_M["Next.js mocks<br/><small>navigation, headers, image</small>"]
end
subgraph Utils["TEST UTILITIES (@test-utils)"]
MR["createMockRequest()"]
MS["createMockSession()"]
MM["createMockModel()"]
end
Mocks --> Unit
Utils --> Unit
SMOKETEST -->|"http://localhost:3000"| DEV["Dev Server"]
style Unit fill:#0f1a0f,stroke:#22c55e,color:#4ade80
style Smoke fill:#1e293b,stroke:#3b82f6,color:#60a5fa
style Mocks fill:#1c1917,stroke:#f59e0b,color:#fbbf24
style Utils fill:#1a1a2e,stroke:#a855f7,color:#c084fc
Directory Structure
src/__tests__/
├── unit/ # Vitest unit tests
│ ├── app/ # Tests for App Router routes (e.g. api/h1/live)
│ ├── db/ # Tests for src/db/* helpers
│ ├── features/ # Tests for src/features/* (archives, galaxy, stats, timeline, admin)
│ ├── queries/ # Tests for src/db/queries/*
│ ├── routes/ # Tests for API route handlers
│ ├── shared/ # Tests for src/shared/* (enums, hooks, utils)
│ ├── update/ # Tests for update pipeline (season, status, fetch, push)
│ ├── utils/ # Tests for src/utils/* (admin, format)
│ └── validators/ # Tests for src/validators/*
├── smoke/ # Vitest smoke tests (against running dev server)
│ └── smoke.test.mjs # Page loads + API endpoint checks
└── utils/ # Shared test utilities
└── index.mjs # Mock factories and helpers
Root-level config files:
| File | Purpose |
|---|---|
vitest.config.mjs | Vitest unit test config (env, aliases, coverage) |
vitest.setup.mjs | Global mocks (auth, Prisma, Next.js modules) |
vitest.smoke.config.mjs | Vitest smoke test config (30s timeout, node env) |
NPM Scripts
| Script | Description |
|---|---|
npm test | Run unit tests then smoke tests sequentially |
npm run test:unit | Vitest single run (unit tests only) |
npm run test:coverage | Vitest with v8 coverage report |
npm run test:e2e | Smoke tests via vitest.smoke.config.mjs |
Note: Smoke tests require a running dev server at http://localhost:3000. Never start the dev server from Claude — ask the user.
Vitest Configuration
Environment: node (default). Component tests opt in to jsdom via per-file comment:
// @vitest-environment jsdom
Globals: true — describe, test, expect, vi are available without import.
Path aliases:
@→./src(matchesjsconfig.json)@test-utils→./src/__tests__/utils
Coverage:
- Provider: v8
- Reporters: text, html
- Excludes:
src/generated/**, test files,src/__tests__/**, malformed enum files
No global thresholds are set yet — add them once meaningful coverage exists.
Global Mocks (vitest.setup.mjs)
The setup file runs before each test file and provides mocks for:
BetterAuth
// Default: logged out (null session)
// Override in tests:
import { auth } from '@/auth';
vi.mocked(auth.api.getSession).mockResolvedValue(createMockSession());
Prisma Client
All models from the schema are mocked with stub CRUD methods (findMany, findUnique, create, update, delete, etc.). The mock targets @/db/db (the singleton export).
import db from '@/db/db';
vi.mocked(db.h1_season.findMany).mockResolvedValue([{ id: 1, season: 1 }]);
Next.js Modules
next/navigation—useRouter,usePathname,useSearchParams,redirect,notFoundnext/headers—headers,cookiesnext/image— passthrough (no optimization)next/link— passthrough
All mocks are cleared via vi.clearAllMocks() in beforeEach.
Test Utilities
Location: src/__tests__/utils/index.mjs (importable as @test-utils)
createMockRequest(url, method, body, headers)
Wraps new Request() for API route handler tests. Sets content-type: application/json by default.
createMockSession(overrides)
Returns a BetterAuth session object with user and session sub-objects (test user, 24h expiry). Merge overrides for custom scenarios.
createMockModel()
Returns a Prisma model stub with all standard CRUD methods as vi.fn(). Useful for ad-hoc mocks beyond what vitest.setup.mjs provides.
Test Naming Conventions
| Type | Pattern | Location |
|---|---|---|
| Unit test | *.test.mjs | src/__tests__/unit/ |
| Smoke test | *.test.mjs | src/__tests__/smoke/ |
| Test utility | *.mjs | src/__tests__/utils/ |
API Route Testing Pattern
App Router route handlers are tested by importing the handler directly:
import { GET } from '@/app/api/healthcheck/route';
test('returns 200 with alive: true', async () => {
const req = new Request('http://localhost/api/healthcheck');
const res = await GET(req);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.alive).toBe(true);
});
For routes that depend on Prisma or auth, mock those modules first — the global mocks in vitest.setup.mjs handle the defaults.
Smoke Test Configuration
Smoke tests use a separate Vitest config (vitest.smoke.config.mjs) — not Playwright.
- Test directory:
src/__tests__/smoke/ - Config:
vitest.smoke.config.mjs - Timeout: 30 seconds per test (network requests to running dev server)
- Base URL:
http://localhost:3000
Gitignore
Test artifacts are excluded from version control:
/coverage # Vitest coverage reports
/test-results/ # Legacy Playwright output
/playwright-report/ # Playwright HTML report
/playwright/ # Playwright screenshots & artifacts
.playwright-mcp/ # Playwright MCP state
