Supertest
A Node.js library for HTTP integration testing. Write fluent request chains against Express, Fastify, Koa, or NestJS apps — with assertions on status codes, response bodies, and headers — without spinning up a real server.
What is Supertest?
Supertest is a Node.js HTTP integration testing library built on top of SuperAgent. It accepts a Node.js HTTP application (Express, Fastify via an HTTP adapter, Koa, NestJS) and makes it easy to send HTTP requests and assert on the responses — all without calling app.listen() or managing ports yourself.
Supertest is ideal for integration tests that cover the full request lifecycle: routing, middleware, validation, business logic, and database interactions together. Because tests hit real route handlers (not mocks), they provide high confidence that your API contracts are correct. Pair Supertest with an in-memory database or transaction rollback for fast, isolated integration tests.
Key Features
- No server management — pass your app directly to request(app); Supertest binds it to an ephemeral port automatically
- Fluent chaining — chain .get(), .post(), .set(), .send(), .expect(), .attach() in a single readable expression
- Built-in assertions — assert on status code, response body, and headers with .expect() without a separate assertion library
- Auth support — set headers with .set('Authorization', 'Bearer token') for JWT or API key authentication
- Cookie sessions — use request.agent(app) to persist cookies across multiple requests in a test
- File uploads — test multipart/form-data routes with .attach(fieldName, filePath)
- Works with any test runner — Jest, Vitest, Mocha, node:test — Supertest returns a Promise
- TypeScript — full types available via @types/supertest
Installation
# Install supertest and types npm install --save-dev supertest @types/supertest # Supertest works with any Node.js HTTP server: # Express, Fastify (via http adapter), Koa, Hapi, NestJS, etc. # Common test runner pairings: npm install --save-dev vitest # modern, fast npm install --save-dev jest @types/jest # classic
Basic Route Tests
Export your app without calling app.listen(). Pass it to request(app) in your tests — Supertest handles the server binding internally.
// app.ts — a minimal Express app to test
import express from 'express';
const app = express();
app.use(express.json());
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'name and email are required' });
}
res.status(201).json({ id: 3, name, email });
});
export default app; // export without .listen() — supertest handles binding // tests/users.test.ts
import request from 'supertest';
import app from '../src/app';
describe('GET /api/users', () => {
it('returns a list of users', async () => {
const res = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body).toHaveLength(2);
expect(res.body[0]).toMatchObject({ id: 1, name: 'Alice' });
});
});
describe('POST /api/users', () => {
it('creates a user with valid data', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Carol', email: '[email protected]' })
.expect(201);
expect(res.body).toMatchObject({ name: 'Carol', email: '[email protected]' });
expect(res.body.id).toBeDefined();
});
it('returns 400 when required fields are missing', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Carol' }) // missing email
.expect(400);
expect(res.body.error).toBe('name and email are required');
});
}); Testing Authentication
Use .set('Authorization', ...) to inject tokens. Generate real JWTs using your own token helper so middleware validation exercises the actual auth logic.
// Testing authenticated endpoints
import request from 'supertest';
import app from '../src/app';
import { generateToken } from '../src/auth/jwt';
describe('Protected routes', () => {
let authToken: string;
beforeAll(() => {
// Generate a real JWT (or a test-specific one)
authToken = generateToken({ id: 1, role: 'admin' });
});
it('returns 401 without a token', async () => {
await request(app)
.get('/api/admin/users')
.expect(401);
});
it('returns 200 with a valid Bearer token', async () => {
const res = await request(app)
.get('/api/admin/users')
.set('Authorization', "Bearer " + authToken)
.expect(200);
expect(Array.isArray(res.body)).toBe(true);
});
it('returns 403 for insufficient role', async () => {
const userToken = generateToken({ id: 2, role: 'user' });
await request(app)
.delete('/api/admin/users/1')
.set('Authorization', "Bearer " + userToken)
.expect(403);
});
}); File Upload Testing
Test file upload endpoints using .attach(fieldName, filePath). Supertest automatically sets the multipart/form-data content type and boundary.
// Testing file uploads with multipart/form-data
import request from 'supertest';
import path from 'path';
import app from '../src/app';
describe('POST /api/upload', () => {
it('uploads a file successfully', async () => {
const res = await request(app)
.post('/api/upload')
.attach('file', path.join(__dirname, 'fixtures/test-image.png'))
.field('description', 'Test upload')
.set('Authorization', 'Bearer test-token')
.expect(200);
expect(res.body.filename).toBeDefined();
expect(res.body.size).toBeGreaterThan(0);
});
it('rejects files that are too large', async () => {
await request(app)
.post('/api/upload')
.attach('file', path.join(__dirname, 'fixtures/large-file.zip'))
.expect(413); // Payload Too Large
});
}); Persistent Sessions with request.agent()
Use request.agent(app) to create a stateful agent that persists cookies across requests. Essential for testing login/logout flows and session-based authentication.
// Persisting cookies across requests with supertest agent
import request from 'supertest';
import app from '../src/app';
describe('Session-based auth', () => {
it('maintains session cookies across requests', async () => {
// Create an agent that persists cookies
const agent = request.agent(app);
// Log in — the agent stores the session cookie
await agent
.post('/api/login')
.send({ email: '[email protected]', password: 's3cret' })
.expect(200);
// Subsequent requests automatically send the stored cookie
const profile = await agent
.get('/api/me')
.expect(200);
expect(profile.body.email).toBe('[email protected]');
// Log out
await agent.post('/api/logout').expect(200);
});
}); Supertest vs Other Approaches
| Approach | Description | Best for |
|---|---|---|
| Supertest | HTTP integration test against real route handlers | API integration tests |
| MSW | Intercept network at Service Worker / Node.js level | Frontend / component tests |
| jest.mock() | Mock HTTP client functions directly | Unit tests, fast isolation |
| Real HTTP | Call a running server via fetch/axios | E2E / contract tests |
Pros and Cons
Pros
- ✓ No server setup — pass the app object directly
- ✓ Fluent API that reads like an HTTP call
- ✓ Tests real route handlers with all middleware active
- ✓ Cookie persistence via request.agent()
- ✓ Framework-agnostic (Express, Fastify, Koa, NestJS, etc.)
Cons
- ✗ Slower than pure unit tests — hits real middleware and (optionally) database
- ✗ Requires app to be exported before .listen() is called
- ✗ Fastify requires wrapping with an HTTP adapter or calling .ready()
- ✗ No built-in test runner — needs Jest, Vitest, or Mocha
- ✗ Not suitable for browser-side or frontend testing
Frequently Asked Questions
What is Supertest?▼
Supertest is a Node.js library for testing HTTP servers. It wraps the SuperAgent HTTP client and lets you make HTTP assertions against an Express, Fastify, Koa, NestJS, or any Node.js http.Server without starting a real server — Supertest binds the server to an ephemeral port internally. Tests are typically integration tests that exercise routes, middleware, and database interactions together.
Does Supertest start a real HTTP server?▼
Supertest handles server binding internally. When you pass your Express app (before calling .listen()) to request(app), Supertest creates an ephemeral server on a random free port, runs the request, and closes the server. You do not need to call app.listen() or manage ports yourself. If you prefer to keep a server running across tests for performance, you can start it manually and pass the server instance to request(server).
How does Supertest work with Jest or Vitest?▼
Supertest is a test-framework-agnostic library — it works with Jest, Vitest, Mocha, or any test runner that supports async/await. Import request from supertest, wrap your assertions in a test() or it() block, and await the request chain. Supertest returns a Promise when you await it, making async/await syntax clean. Pair it with a beforeAll/afterAll database setup for integration tests.
How do I test routes that require authentication?▼
Use .set('Authorization', 'Bearer <token>') to inject auth headers on each request. Generate a real JWT using your actual token generation function (with a test secret) so middleware validation passes correctly. For session-based auth using cookies, use request.agent(app) which persists cookies across multiple requests within the same agent instance.
What is the difference between request(app) and request.agent(app)?▼
request(app) creates a one-off request and does not persist any state (cookies, sessions) between calls. request.agent(app) creates a persistent agent that stores cookies returned by the server and sends them on subsequent requests — the same behavior as a browser session. Use the agent when testing login/logout flows or any scenario that relies on session cookies being maintained.
How do I test file uploads with Supertest?▼
Use .attach('fieldName', filePath) to attach a file to a multipart/form-data POST request. Add additional form fields with .field('key', 'value'). Supertest internally sets the Content-Type to multipart/form-data with the correct boundary. Combine .attach() with .set() for headers (authorization, custom headers) as needed.
Related Products
on GumroadDeveloper Productivity Bundle
Stop rebuilding the same setup every project. 51 VSCode snippets, 40 Git aliases, 5 Docker Compose stacks, 5 GitHub Actions workflows, 5 README templates, and a dotfiles kit. Set up a professional dev environment in under an hour.
Buy on Gumroad →AI Prompt Engineering Toolkit
58 structured prompt templates for Claude, ChatGPT, and Gemini — organized by use case. Code Review, Debugging, Documentation, Architecture, and 10 model-specific tips. Stop writing prompts from scratch.
Buy on Gumroad →Developer Cheat Sheet Bundle
10 print-ready cheat sheets for the tools developers use every day: Git, Docker, Linux CLI, SQL, Regex, HTTP Status codes, VS Code shortcuts, Bash scripting, jq/JSON, and Markdown.
Buy on Gumroad →You Might Also Need
Vitest Configuration Generator
Generate a complete vitest.config.ts visually. Configure test environment (node/jsdom/happy-dom), coverage provider, reporters, thresholds, and path aliases.
Open free tool →
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
Jest Coverage Config Generator
Generate Jest configuration with coverage thresholds, reporters, and collectCoverageFrom patterns. Set line, function, branch, and statement thresholds.
Open free tool →
More Free Tools
Related Articles
MSW 2.0: The Best Way to Mock APIs in Frontend Tests
A complete guide to Mock Service Worker (MSW) 2.0 — setup, request handlers, Vitest and Jest integration, Storybook mocking, and best practices for realistic API mocking in frontend tests.
Modern JavaScript Testing 2026: Vitest vs Jest vs Playwright
Complete guide to JavaScript testing in 2026. Compare Vitest vs Jest for unit tests, Playwright vs Cypress for E2E, component testing, coverage tools, and CI/CD integration.
Vitest: The Fast Unit Testing Framework for Vite Projects - 2026 Guide
Complete guide to Vitest in 2026: setup, configuration, mocking, coverage, watch mode, and a detailed comparison with Jest to help you decide which testing framework fits your project.
Vitest vs Jest vs Mocha: Complete Testing Framework Comparison 2026
A comprehensive comparison of Vitest, Jest, and Mocha for JavaScript and TypeScript projects in 2026. Covers speed benchmarks, configuration, TypeScript support, mocking, code coverage, and migration strategies.
Vitest vs Jest 2026: Modern Testing Comparison
Comprehensive comparison of Vitest vs Jest in 2026: speed benchmarks, ESM support, migration guide, snapshot testing, mocking, watch mode, and coverage reporting.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →