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 Authentication

Testing protected endpoints

Many endpoints require authentication. Tests for these endpoints must send valid tokens and verify that unauthenticated requests are rejected.

Testing 401 (not authenticated)

describe("protected endpoints", () => {
  test("returns 401 without Authorization header", async () => {
    const response = await app.fetch(new Request("http://localhost/v2/admin/books"));

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

  test("returns 401 with invalid token", async () => {
    const response = await app.fetch(
      new Request("http://localhost/v2/admin/books", {
        headers: { Authorization: "Bearer invalid-token" },
      }),
    );

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

  test("returns 401 with expired token", async () => {
    const expiredToken = createTestToken({ userId: "u1", role: "admin" }, { expiresIn: -1 });
    const response = await app.fetch(
      new Request("http://localhost/v2/admin/books", {
        headers: { Authorization: `Bearer ${expiredToken}` },
      }),
    );

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

Creating test tokens

Build a helper that creates tokens for testing:

// tests/helpers/auth.ts
import jwt from "jsonwebtoken";

const TEST_SECRET = "test-secret-key";

export function createTestToken(
  payload: { userId: string; role?: string },
  options: { expiresIn?: number } = {},
): string {
  return jwt.sign(payload, TEST_SECRET, {
    expiresIn: options.expiresIn ?? 3600,
  });
}

export function authHeader(userId: string, role: string = "user"): Record<string, string> {
  return { Authorization: `Bearer ${createTestToken({ userId, role })}` };
}

[!NOTE] The Authentication course built JWT-based auth. The Testing Auth and Security course tested auth flows specifically. This lesson shows how to test protected endpoints in general — any route that requires authentication.

Testing with authentication

describe("POST /v2/books (authenticated)", () => {
  test("creates book when authenticated", async () => {
    const response = await app.fetch(
      jsonRequest(
        "POST",
        "/v2/books",
        {
          title: "Kindred",
          authorId: "a1",
          genre: "science-fiction",
        },
        authHeader("u1"),
      ),
    );

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

  test("returns 401 without authentication", async () => {
    const response = await app.fetch(
      jsonRequest("POST", "/v2/books", {
        title: "Kindred",
        authorId: "a1",
        genre: "science-fiction",
      }),
    );

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

The jsonRequest helper from earlier accepts an optional headers argument. authHeader("u1") adds the Authorization header with a valid test token.

Testing 403 (not authorized)

describe("DELETE /v2/books/:id (admin only)", () => {
  test("allows admin to delete", async () => {
    const response = await app.fetch(
      new Request("http://localhost/v2/books/b1", {
        method: "DELETE",
        headers: authHeader("u1", "admin"),
      }),
    );

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

  test("returns 403 for non-admin user", async () => {
    const response = await app.fetch(
      new Request("http://localhost/v2/books/b1", {
        method: "DELETE",
        headers: authHeader("u2", "user"),
      }),
    );

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

  test("returns 401 without authentication", async () => {
    const response = await app.fetch(
      new Request("http://localhost/v2/books/b1", {
        method: "DELETE",
      }),
    );

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

Three states: no auth (401), wrong role (403), correct role (success). Each has its own test.

Exercises

Exercise 1: Create the authHeader helper. Test a protected endpoint with and without the header.

Exercise 2: Test the three states: no token (401), valid token wrong role (403), valid token correct role (success).

Exercise 3: Test with an expired token. Verify 401.

Why test both 401 and 403 separately?

← Testing Error Responses Factories and Fixtures →

© 2026 hectoday. All rights reserved.