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

Testing Edge Cases

Beyond the happy path

Happy path tests verify that valid input produces correct output. Edge case tests verify that unusual, extreme, or malicious input is handled gracefully — not with crashes, data corruption, or security vulnerabilities.

Empty and whitespace inputs

describe("edge cases: empty input", () => {
  test("rejects empty string title", async () => {
    const response = await app.fetch(
      post("/v2/books", {
        title: "",
        authorId: "a1",
        genre: "fiction",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("rejects whitespace-only title", async () => {
    const response = await app.fetch(
      post("/v2/books", {
        title: "   ",
        authorId: "a1",
        genre: "fiction",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("handles empty query parameter gracefully", async () => {
    const response = await app.fetch(get("/v2/books?genre="));
    // Should either ignore the empty genre or return empty results
    expect([200, 400]).toContain(response.status);
  });
});

[!NOTE] The Zod course’s .trim().min(1) pattern catches whitespace-only strings. These tests verify the schema works correctly in the route context.

Special characters

describe("edge cases: special characters", () => {
  test("handles unicode in title", async () => {
    const author = createAuthor();
    const response = await app.fetch(
      post("/v2/books", {
        title: "日本語の本",
        authorId: author.id,
        genre: "fiction",
      }),
    );

    expect(response.status).toBe(201);
    const book = await response.json();
    expect(book.title).toBe("日本語の本");
  });

  test("handles emoji in description", async () => {
    const author = createAuthor();
    const response = await app.fetch(
      post("/v2/books", {
        title: "Good Book",
        authorId: author.id,
        genre: "fiction",
        description: "This book is great! 📚🌟",
      }),
    );

    expect(response.status).toBe(201);
  });

  test("query param with special characters", async () => {
    const response = await app.fetch(get("/v2/books?genre=science-fiction"));
    expect(response.status).toBe(200);
  });
});

Large payloads

describe("edge cases: large payloads", () => {
  test("rejects title exceeding max length", async () => {
    const response = await app.fetch(
      post("/v2/books", {
        title: "A".repeat(10000),
        authorId: "a1",
        genre: "fiction",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("rejects description exceeding max length", async () => {
    const response = await app.fetch(
      post("/v2/books", {
        title: "Book",
        authorId: "a1",
        genre: "fiction",
        description: "X".repeat(100000),
      }),
    );
    expect(response.status).toBe(400);
  });
});

Boundary values

describe("edge cases: boundary values", () => {
  test("accepts rating of exactly 1 (minimum)", async () => {
    const book = createBook();
    const response = await app.fetch(
      post(`/v2/books/${book.id}/reviews`, {
        rating: 1,
        userId: "u1",
      }),
    );
    expect(response.status).toBe(201);
  });

  test("accepts rating of exactly 5 (maximum)", async () => {
    const book = createBook();
    const response = await app.fetch(
      post(`/v2/books/${book.id}/reviews`, {
        rating: 5,
        userId: "u1",
      }),
    );
    expect(response.status).toBe(201);
  });

  test("rejects rating of 0 (below minimum)", async () => {
    const book = createBook();
    const response = await app.fetch(
      post(`/v2/books/${book.id}/reviews`, {
        rating: 0,
        userId: "u1",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("rejects rating of 6 (above maximum)", async () => {
    const book = createBook();
    const response = await app.fetch(
      post(`/v2/books/${book.id}/reviews`, {
        rating: 6,
        userId: "u1",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("page 1 is valid (minimum)", async () => {
    const response = await app.fetch(get("/v2/books?page=1"));
    expect(response.status).toBe(200);
  });

  test("page 0 is invalid", async () => {
    const response = await app.fetch(get("/v2/books?page=0"));
    expect(response.status).toBe(400);
  });
});

Boundary value testing checks the edges of valid ranges: the minimum valid value, the maximum valid value, one below minimum, and one above maximum.

Type confusion

describe("edge cases: type confusion", () => {
  test("rejects number as title", async () => {
    const response = await app.fetch(
      post("/v2/books", {
        title: 42,
        authorId: "a1",
        genre: "fiction",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("rejects string as rating", async () => {
    const book = createBook();
    const response = await app.fetch(
      post(`/v2/books/${book.id}/reviews`, {
        rating: "five",
        userId: "u1",
      }),
    );
    expect(response.status).toBe(400);
  });

  test("rejects null body", async () => {
    const response = await app.fetch(
      new Request("http://localhost/v2/books", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: "null",
      }),
    );
    expect(response.status).toBe(400);
  });
});

Exercises

Exercise 1: Write boundary value tests for all numeric fields (ratings, page, limit). Test min, max, below min, above max.

Exercise 2: Write special character tests. Create a book with unicode, emoji, and HTML characters in the title.

Exercise 3: Write type confusion tests. Send numbers where strings are expected, strings where numbers are expected, null, and undefined.

Why test boundary values (min, max, min-1, max+1)?

← Testing Background Jobs Test Organization →

© 2026 hectoday. All rights reserved.