hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Testing APIs with @hectoday/http

Why Test

  • What Testing Gives You
  • Types of Tests
  • Project Setup

Unit Testing

  • Testing Pure Functions
  • Testing Zod Schemas
  • Testing Business Logic

Integration Testing

  • Testing Route Handlers
  • Testing GET Endpoints
  • Testing POST Endpoints
  • Testing Error Responses
  • Testing Authentication

Test Helpers

  • Factories and Fixtures
  • Test Database Isolation
  • Request Helpers

Advanced Testing

  • Mocking External Services
  • Testing Background Jobs
  • Testing Edge Cases

Putting It All Together

  • Test Organization
  • Checklist and Capstone

Project Setup

The testing stack

This course uses Vitest — a fast, TypeScript-native test runner. It runs tests in parallel, supports ESM imports, and has a familiar API (compatible with Jest).

Adding Vitest

Start with the book catalog API from the Caching or API Versioning course:

npm install -D vitest

Add a test script to package.json:

{
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

vitest run executes all tests once (for CI). vitest runs in watch mode (re-runs on file changes).

Vitest configuration

Create vitest.config.ts:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    include: ["tests/**/*.test.ts"],
    setupFiles: ["tests/setup.ts"],
  },
});

globals: true makes describe, test, expect available without importing (optional — you can import them explicitly). setupFiles runs before all tests — used for database setup.

Test file structure

tests/
  setup.ts                    # Global setup (test database)
  unit/
    transformers.test.ts      # Unit tests for response transformers
    schemas.test.ts           # Unit tests for Zod schemas
    helpers.test.ts           # Unit tests for helper functions
  integration/
    books.test.ts             # Integration tests for book endpoints
    reviews.test.ts           # Integration tests for review endpoints
    errors.test.ts            # Integration tests for error responses
  helpers/
    factories.ts              # Test data factories
    request.ts                # Request helper functions

Unit tests in tests/unit/. Integration tests in tests/integration/. Test helpers in tests/helpers/.

The test database

Tests should not use the production database. Create a separate test database:

// tests/setup.ts
import Database from "better-sqlite3";

// Use an in-memory database for tests — fast and isolated
const testDb = new Database(":memory:");
testDb.pragma("journal_mode = WAL");
testDb.pragma("foreign_keys = ON");

// Create the schema (same as production)
testDb.exec(`
  CREATE TABLE authors (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    bio TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
  );
  CREATE TABLE books (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    author_id TEXT NOT NULL,
    genre TEXT NOT NULL,
    description TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (author_id) REFERENCES authors(id)
  );
  CREATE TABLE reviews (
    id TEXT PRIMARY KEY,
    book_id TEXT NOT NULL,
    user_id TEXT NOT NULL,
    rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
    body TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (book_id) REFERENCES books(id)
  );
`);

export { testDb };

[!NOTE] SQLite’s :memory: database exists only in RAM. It is fast (no disk I/O), isolated (each test run starts fresh), and disposable (gone when the process ends). The Database Design course covered in-memory databases for this exact use case.

Your first test

// tests/unit/transformers.test.ts
import { describe, test, expect } from "vitest";

describe("example", () => {
  test("1 + 1 equals 2", () => {
    expect(1 + 1).toBe(2);
  });
});
npm test
# ✓ tests/unit/transformers.test.ts (1 test)
# Test Files  1 passed (1)
# Tests  1 passed (1)

The test runner finds files matching tests/**/*.test.ts, runs them, and reports results. Green means pass. Red means fail.

Exercises

Exercise 1: Install Vitest. Create vitest.config.ts. Write and run a trivial test.

Exercise 2: Create the test database setup file with an in-memory SQLite database.

Exercise 3: Create the test directory structure: unit/, integration/, helpers/.

Why use an in-memory database for tests instead of the production database?

← Types of Tests Testing Pure Functions →

© 2026 hectoday. All rights reserved.