The Codex / Technologies / Express.js / Testing Express Apps

Testing Express Apps

Integration testing API endpoints with supertest - testing routes without starting a real server, mocking the database layer, and what to actually test.

What it is, in plain English: Testing an Express app means sending real HTTP-like requests to your routes and asserting on the response -- status code, body shape, headers -- without actually binding to a network port. Supertest is the standard library for this: it wraps your Express app object directly, letting tests run fast and in-process. Combined with Vitest or Jest as the test runner, this is how Express APIs are tested in production codebases.

Testing routes without starting a real server

Step 1 -- structure your app for testability

// app.js -- defines the app, but does NOT start listening:
import express from "express";
const app = express();
app.use(express.json());

app.get("/health", (req, res) => res.json({ status: "ok" }));

export default app;
// server.js -- the actual entry point, separate from app.js:
import app from "./app.js";
app.listen(3000, () => console.log("Running"));

// This split matters: tests import app.js and never touch server.js,
// so they never actually bind to a port.

Step 2 -- install supertest

# npm install -D supertest vitest

A basic test

import request from "supertest";
import { test, expect } from "vitest";
import app from "../app.js";

test("GET /health returns ok status", async () => {
  const response = await request(app).get("/health");

  expect(response.status).toBe(200);
  expect(response.body).toEqual({ status: "ok" });
});

Testing POST requests with a body

test("POST /users creates a user", async () => {
  const response = await request(app)
    .post("/users")
    .send({ name: "Alice", email: "alice@example.com" });

  expect(response.status).toBe(201);
  expect(response.body).toMatchObject({ name: "Alice" });
  expect(response.body.id).toBeDefined();
});

test("POST /users rejects missing email", async () => {
  const response = await request(app)
    .post("/users")
    .send({ name: "Alice" });   // missing email

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

Testing protected routes

test("GET /profile requires authentication", async () => {
  const response = await request(app).get("/profile");
  expect(response.status).toBe(401);
});

test("GET /profile works with a valid token", async () => {
  const token = generateTestToken({ userId: 1 });
  const response = await request(app)
    .get("/profile")
    .set("Authorization", `Bearer ${token}`);

  expect(response.status).toBe(200);
});
Try It Yourself (Node.js logic)

Official Sources

The Codex links only to official documentation. No third-party tutorials.