Test Pyramid for Static Sites

Layer unit, integration, and acceptance tests so each catches what the others cannot in a static site with a backend API

Tags

Test Pyramid for Static Sites

The Lesson

A static site with a backend API needs three distinct testing layers — fast unit tests for logic correctness, live integration tests for service interactions, and browser-based acceptance tests for the rendered result. Each layer catches a different failure class, and skipping any one of them creates a blind spot that the others cannot cover.

Context

Lessons Hub is a static Astro site backed by a FastAPI RAG API. The build pipeline harvests markdown from multiple repositories, validates schemas, builds a vector corpus, renders HTML, and indexes for search. The backend serves chat queries using Ollama (LLM) and ChromaDB (vector store). The system spans Python harvesting scripts, TypeScript rendering, and browser-visible output — a single testing approach cannot cover all three.

What Happened

  1. The project started with 76 pytest unit tests covering the Python harvesting pipeline: slug generation, frontmatter parsing, YAML schema validation, JSON output structure, and edge cases like duplicate IDs and missing fields.
  2. The backend grew to 138 pytest tests covering health endpoints, chat flow, retrieval, adapter factories, gap detection, discovery scoring, and cloud adapter initialization — all using mocked infrastructure.
  3. When Ollama and ChromaDB were running locally, live integration tests validated that the real RAG pipeline produced grounded responses with proper citations — something mocks fundamentally cannot verify.
  4. After both test layers passed, 105 broken links were discovered in production by a Playwright crawler. The bug was in the Astro rendering layer (link rewriting), invisible to Python tests because they never touch the final HTML.
  5. The three layers were formalized: unit (pytest, fast, no infrastructure), integration (pytest, requires live services), acceptance (Playwright, requires a running site).

Key Insights

Examples

Layer Comparison

Layer Tool Speed Infrastructure What it catches
Unit pytest ~5s for 214 tests None Logic bugs, regressions, edge cases
Integration pytest + live services ~30s Ollama, ChromaDB Service interaction, data format, pipeline correctness
Acceptance Playwright ~60s for 80 pages Deployed/preview site Broken links, UI regressions, routing errors

What Each Layer Missed

Bug Unit Integration Acceptance
Relative .md links broken after rendering Cannot detect (doesn't render HTML) Cannot detect (doesn't test the site) Caught (crawled all pages)
LLM returning malformed citations Mocked away Caught (real LLM response) Cannot detect (doesn't test API)
Duplicate slug from new repo Caught (slug collision test) Irrelevant Would show as duplicate page
Pagefind search white in dark mode Cannot detect Cannot detect Caught (smoke test)

Applicability

This three-layer model applies to any system where:

It is less necessary for:

Related Lessons

Related Lessons