Testing
Testing strategy and load test results
Test Strategy
Respondeo uses unit tests, integration tests that run against real backing services, and k6 load tests.
Unit Tests
Unit tests are colocated with the code they cover, as *.test.ts next to the
module. They need no database or cache and are the bulk of the suite:
| Area | File |
|---|---|
| Attempt grading and de-duplication | apps/web/lib/quiz/grading.test.ts |
| Signed attempt-start tokens and timing | apps/web/lib/quiz/attempt-token.test.ts |
| Pagination parameter parsing | apps/web/lib/pagination.test.ts |
| Attempt submission schemas | apps/web/lib/validations/attempt.test.ts |
| Rate limiting | apps/web/lib/rate-limit.test.ts |
| Redis target formatting | apps/web/lib/cache/client.test.ts |
| CLI project-name validation | packages/create-respondeo-app/src/validate.test.ts |
Run them from the repository root:
pnpm testThis runs in CI on every push and pull request.
Integration Tests
Integration tests are named *.integration.test.ts and exercise behaviour that
only a real service demonstrates — foreign-key cascades, transaction rollback,
and Redis SCAN-based invalidation:
| Area | File |
|---|---|
| Quiz content persistence, cascades, rollback | apps/web/lib/quiz/content.integration.test.ts |
| Cache invalidation against Valkey | apps/web/lib/cache/invalidation.integration.test.ts |
They skip themselves unless DATABASE_URL / REDIS_URL are set, so
pnpm test stays fast and dependency-free. To run everything against the local
Docker Compose stack:
pnpm --filter web test:integrationThat script starts Postgres and Valkey, applies migrations, and runs the full
suite. Credentials come from apps/web/.env.compose, which is created from
.env.compose.example on first run. The containers can be managed directly
with pnpm --filter web docker:up and pnpm --filter web docker:down.
Load Tests
Load tests simulate high-traffic scenarios using k6.
Load Test Results
The app has been tested to sustain 5000 concurrent users with 0% error rate.
Latest Results (January 4, 2026)
| Metric | Value |
|---|---|
| Peak VUs | 5000 (3000 browsers, 1500 players, 500 spectators) |
| Error Rate | 0.00% ✅ |
| Throughput | 834 req/s |
| Total Requests | 319,511 |
| Test Duration | 6m 23s |
| Data Transferred | 1.7 GB received, 109 MB sent |
Performance Optimizations
The following optimizations enabled this performance level:
- Redis/Valkey caching — 5-10 minute TTL on read-heavy data
- PostgreSQL tuning — Increased connections and memory
- Cached quiz data for attempts — Eliminates 3 DB queries per attempt
Endpoint Latency
| Endpoint | Median | p90 | p95 | p99 |
|---|---|---|---|---|
| Quiz List | 9.31ms | 295ms | 1.04s | 2.32s |
| Quiz Detail | 9.65ms | 311ms | 1.07s | 2.31s |
| Attempt Submit | 20.19ms | 532ms | 1.32s | 2.67s |
| Global Leaderboard | 8.86ms | 330ms | 1.05s | 2.34s |
| Quiz Leaderboard | 9.08ms | 374ms | 1.14s | 2.40s |
Notes:
- Median latencies are excellent (~10ms) thanks to caching
- p95+ latencies are elevated under peak load but acceptable
- Write path (attempt submission) has highest latency as expected
Comparison: Before vs After Caching
| Metric | Without Cache | With Cache | Improvement |
|---|---|---|---|
| Max Stable VUs | ~1,600 | 5,000 | 3.1x |
| Error Rate | 0.01% | 0.00% | ✅ |
| Throughput | ~400 req/s | 834 req/s | 2x |
| Median Latency | ~15ms | ~10ms | 33% faster |
Running Load Tests
Prerequisites
Install k6:
# macOS
brew install k6
# Linux
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
# Windows
choco install k6Run Load Test
# Start the backing services and the app
pnpm --filter web docker:up
pnpm dev
# In another terminal (requires k6 on your PATH)
pnpm --filter web loadtestThe load test script is located at apps/web/tests/loadtest.js.
Test Scenarios
The load test simulates three user types:
- Browsers (60%) — View quiz list and details
- Players (30%) — Play quizzes and submit attempts
- Spectators (10%) — View leaderboards
Customizing Load Tests
Edit apps/web/tests/loadtest.js:
export const options = {
stages: [
{ duration: "30s", target: 100 }, // Ramp up to 100 users
{ duration: "1m", target: 100 }, // Stay at 100 users
{ duration: "30s", target: 0 }, // Ramp down to 0
],
thresholds: {
http_req_failed: ["rate<0.01"], // <1% errors
http_req_duration: ["p(95)<1000"], // 95% requests <1s
},
};Performance Monitoring
Database
Monitor PostgreSQL performance:
docker compose exec db psql -U quiz_app -c "SELECT * FROM pg_stat_activity;"Cache
Monitor Redis/Valkey:
docker compose exec valkey redis-cli INFO stats
docker compose exec valkey redis-cli INFO memoryApplication
Next.js performance monitoring is available through:
- Vercel Analytics (if deployed on Vercel)
- Custom logging in server actions and API routes
Next Steps
- Architecture — System architecture
- Caching Guide — Caching configuration
- Database Guide — Database optimization