Add Vitest integration tests for SvelteKit load functions #123

Closed
opened 2026-03-27 18:36:19 +01:00 by marcel · 1 comment
Owner

Problem

The project has significant server-side logic in +page.server.ts files — document search, person detail, auth checks, error handling. None of these load functions are tested at the unit/integration layer. The E2E suite covers them incidentally, but that is the wrong layer: a load function that returns the wrong status code should be caught in milliseconds by a focused test, not discovered after a full Playwright run.

Why This Matters

SvelteKit load functions are plain TypeScript — they take a fetch and params argument and return data or throw an error. They are trivial to unit test by importing directly. Not testing them means:

  • Error paths (404, 403, backend down) are only covered by E2E tests that are expensive to run and harder to diagnose
  • Refactors to load functions have no fast feedback loop
  • API contract changes (renamed fields, changed status codes) aren't caught until the browser renders wrongly

What Needs To Be Done

For each critical server route, add a Vitest test file (in the server project, Node environment) that imports load directly:

// src/routes/documents/[id]/+page.server.spec.ts
import { load } from './+page.server';
import { describe, it, expect, vi } from 'vitest';

describe('document detail load', () => {
  it('returns document data for valid id', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({ id: '123', title: 'Test Doc' })
    });
    const result = await load({ params: { id: '123' }, fetch: mockFetch });
    expect(result.document.title).toBe('Test Doc');
  });

  it('throws 404 when document does not exist', async () => {
    const mockFetch = vi.fn().mockResolvedValue({ ok: false, status: 404 });
    await expect(load({ params: { id: 'missing' }, fetch: mockFetch }))
      .rejects.toMatchObject({ status: 404 });
  });
});

Priority routes to cover:

  • routes/+page.server.ts (document search with filters)
  • routes/documents/[id]/+page.server.ts (document detail)
  • routes/persons/[id]/+page.server.ts (person detail)
  • routes/admin/+page.server.ts (admin — permission checks)

Each test should cover: happy path, not found (404), forbidden (403), and backend error (500).

Acceptance Criteria

  • Load function tests exist for all 4 priority routes
  • Happy path, 404, 403, and 500 cases covered per route
  • Tests run in the Vitest server project (Node environment, not browser)
  • Tests run in under 1 second total (no real network calls)
## Problem The project has significant server-side logic in `+page.server.ts` files — document search, person detail, auth checks, error handling. None of these `load` functions are tested at the unit/integration layer. The E2E suite covers them incidentally, but that is the wrong layer: a `load` function that returns the wrong status code should be caught in milliseconds by a focused test, not discovered after a full Playwright run. ## Why This Matters SvelteKit `load` functions are plain TypeScript — they take a `fetch` and `params` argument and return data or throw an error. They are trivial to unit test by importing directly. Not testing them means: - Error paths (404, 403, backend down) are only covered by E2E tests that are expensive to run and harder to diagnose - Refactors to `load` functions have no fast feedback loop - API contract changes (renamed fields, changed status codes) aren't caught until the browser renders wrongly ## What Needs To Be Done For each critical server route, add a Vitest test file (in the `server` project, Node environment) that imports `load` directly: ```typescript // src/routes/documents/[id]/+page.server.spec.ts import { load } from './+page.server'; import { describe, it, expect, vi } from 'vitest'; describe('document detail load', () => { it('returns document data for valid id', async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ id: '123', title: 'Test Doc' }) }); const result = await load({ params: { id: '123' }, fetch: mockFetch }); expect(result.document.title).toBe('Test Doc'); }); it('throws 404 when document does not exist', async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: false, status: 404 }); await expect(load({ params: { id: 'missing' }, fetch: mockFetch })) .rejects.toMatchObject({ status: 404 }); }); }); ``` Priority routes to cover: - `routes/+page.server.ts` (document search with filters) - `routes/documents/[id]/+page.server.ts` (document detail) - `routes/persons/[id]/+page.server.ts` (person detail) - `routes/admin/+page.server.ts` (admin — permission checks) Each test should cover: happy path, not found (404), forbidden (403), and backend error (500). ## Acceptance Criteria - [ ] Load function tests exist for all 4 priority routes - [ ] Happy path, 404, 403, and 500 cases covered per route - [ ] Tests run in the Vitest `server` project (Node environment, not browser) - [ ] Tests run in under 1 second total (no real network calls)
marcel added the test label 2026-03-27 18:45:09 +01:00
Author
Owner

Architect review (@mkeller): Well-reasoned and correctly scoped. Do this alongside #119.

One clarification: the title calls these "integration tests" but the implementation mocks fetch — these are unit tests. That is fine and appropriate; just name them accurately in the file and test descriptions. The distinction matters because these tests will not catch API contract drift. If the backend renames a field, the mocked load tests keep passing. That is acceptable — document it explicitly. These tests cover load function logic and error branching; E2E tests cover the actual contract.

The four priority routes listed (home search, document detail, person detail, admin) are the right ones to start with.

**Architect review (@mkeller):** ✅ Well-reasoned and correctly scoped. Do this alongside #119. One clarification: the title calls these "integration tests" but the implementation mocks `fetch` — these are unit tests. That is fine and appropriate; just name them accurately in the file and test descriptions. The distinction matters because these tests will not catch API contract drift. If the backend renames a field, the mocked load tests keep passing. That is acceptable — document it explicitly. These tests cover load function logic and error branching; E2E tests cover the actual contract. The four priority routes listed (home search, document detail, person detail, admin) are the right ones to start with.
Sign in to join this conversation.
No Label test
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#123