HTTP Testing with Supertest

Fire Real HTTP Requests Without Binding a Port

HTTP Testing with Supertest

Supertest wraps app.callback() so you can make real HTTP assertions against your Koa routes without occupying a port or managing server lifecycle.

3 min read Level 2/5 #koa#production#testing
What you'll learn
  • Pass app.callback() to supertest to create a test agent
  • Assert on status codes, headers, and JSON response bodies
  • Test authenticated routes by setting request headers

Supertest is the standard library for integration-testing Node HTTP servers. It accepts any function with the signature (req, res) => void — exactly what app.callback() returns — so it works with Koa without any adapter.

Installation

npm i -D supertest

Basic Request and Assertion

// app.js
import Koa from "koa";
import Router from "@koa/router";

const app = new Koa();
const router = new Router();

router.get("/ping", (ctx) => {
  ctx.body = { ok: true };
});

app.use(router.routes());
export default app;
// app.test.js
import request from "supertest";
import app from "./app.js";

describe("GET /ping", () => {
  it("returns 200 with ok:true", async () => {
    const res = await request(app.callback()).get("/ping");
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ ok: true });
  });
});

app.callback() hands Supertest the underlying Node http.IncomingMessage handler. No app.listen() call is needed, so no port is bound and no teardown is required.

Asserting Headers and Errors

it("returns 404 for unknown routes", async () => {
  const res = await request(app.callback()).get("/unknown");
  expect(res.status).toBe(404);
});

it("echoes the Accept-Language header", async () => {
  const res = await request(app.callback())
    .get("/ping")
    .set("Accept-Language", "fr");
  expect(res.headers["content-type"]).toMatch(/json/);
});

Testing Authenticated Routes

Pass your token or cookie in .set() or .auth():

it("rejects requests without a token", async () => {
  const res = await request(app.callback()).get("/api/me");
  expect(res.status).toBe(401);
});

it("accepts a valid Bearer token", async () => {
  const res = await request(app.callback())
    .get("/api/me")
    .set("Authorization", "Bearer test-token-abc");
  expect(res.status).toBe(200);
});

Reusing an Agent

If your routes set cookies, use request.agent() to persist them across calls:

const agent = request.agent(app.callback());
await agent.post("/login").send({ user: "ada", pass: "secret" });
const res = await agent.get("/dashboard"); // cookie is sent automatically
expect(res.status).toBe(200);

Up Next

Capture structured logs from every request with Pino and koa-pino-logger.

Structured Logging →