Supertest & HTTP Integration
Supertest sends HTTP requests to Express and Fastify apps in-process without binding a port, making integration tests fast and parallel-safe.
Search across all documentation pages
Supertest sends HTTP requests to Express and Fastify apps in-process without binding a port, making integration tests fast and parallel-safe.
Quick-reference recipe card - copy-paste ready.
import request from "supertest";
import { createApp } from "../src/app.js";
const res = await request(createApp()).get("/health");npm install -D supertest @types/supertestWhen to reach for this:
// src/app.ts
import express from "express";
export function createApp() {
const app = express();
app.use(express.json());
app.get("/health", (_req, res) => {
res.json({ status: "ok" });
});
app.post("/items", (req, res) => {
if (!req.body?.name) {
return res.status(400).json({ error: "name required" });
}
res.status(201).json({ id: "item_1", name: req.body.name });
});
return app;
}// test/app.http.test.ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import request from "supertest";
import { createApp } from "../src/app.js";
describe("HTTP API", () => {
it("GET /health returns ok", async () => {
const res = await request(createApp()).get("/health");
assert.equal(res.status, 200);
assert.deepEqual(res.body, { status: "ok" });
});
it("POST /items validates body", async () => {
const res = await request(createApp())
.post("/items")
.send({})
.set("Content-Type", "application/json");
assert.equal(res.status, 400);
assert.equal(res.body.error, "name required");
});
});What this demonstrates:
createApp() factory avoids listen() side effects.node:test (no Jest required).res.body parsed JSON, not raw strings.EADDRINUSE.app.inject() natively or Supertest after app.ready().import Fastify from "fastify";
const app = Fastify();
app.get("/health", async () => ({ ok: true }));
await app.ready();
const res = await app.inject({ method: "GET", url: "/health" });
// res.statusCode, res.json()createApp() return as express.Application or FastifyInstance for editor help.server.ts.await promise in async tests. Fix: always await request(...).get().request.agent(app) only when testing sessions; fresh request(app) otherwise.| Alternative | Use When | Don't Use When |
|---|---|---|
Fastify inject | Fastify-only codebase | Express apps |
| Real port + fetch | Testing TLS or HTTP/2 edge | Default integration |
| Pact contract tests | Consumer-provider boundary | Single monolith API |
Yes. Supertest attaches to Express Application HTTP handler as in Express 4.
await request(app).get("/me").set("Authorization", "Bearer test-token");Use test tokens or override auth middleware in createApp({ auth: "test" }).
Supertest supports .attach() for multipart form data.
Supertest is HTTP-only. Use ws client against test server or dedicated WS test utils.
@nestjs/testing creates app; use Supertest request(app.getHttpServer()).
Port binding is slower, flaky in parallel CI, and requires separate process lifecycle management.
Inject repository mocks when calling createApp({ repos: mocks }) - avoid mocking at HTTP layer only.
Yes - integration tests catch auth-before-parser mistakes Supertest exercises full stack.
Assert on res.text or stream events; more complex - consider focused unit tests on stream handlers.
Both work; choose repo standard runner; Supertest API unchanged.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026