Skip to main content

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.

✓ No server setup required ✓ Fluent assertion chaining ✓ Auth header and cookie support ✓ File upload testing ✓ Works with Jest and Vitest ✓ TypeScript types included

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

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
SupertestHTTP integration test against real route handlersAPI integration tests
MSWIntercept network at Service Worker / Node.js levelFrontend / component tests
jest.mock()Mock HTTP client functions directlyUnit tests, fast isolation
Real HTTPCall a running server via fetch/axiosE2E / 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.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Vitest Configuration Generator OpenAPI Validator Jest Coverage Config Generator Jest Testing Framework Guide Vitest Guide — Vite-native Testing Jest to Vitest Migrator API Test Case Generator API Response Mock Generator API Response Time Tester OpenAPI Schema Validator OpenAPI Mock Generator API Request Builder API Mock Server Config Generator Jest Mock Generator API Rate Limit Calculator API Response Formatter & Analyzer HTTP API Tester Rate Limit Calculator LLM Cost Calculator CORS Policy Builder package.json Generator AI Prompt Cost Calculator Cron Expression Validator REST API Request Builder Cron Expression Explainer Cypress Frontend Testing Guide Mock Service Worker (MSW) API Mocking Guide LLM Prompt Cost Calculator Test Coverage Calculator API Response Mocker AI Rate Limit Calculator API Key Generator AI Doc Generator — Pro Tool AI Regex Explainer — Pro Tool AI Unit Test Generator — Pro Tool Cron Expression Builder Cron Expression Generator Cron Expression Validator GitHub Actions Workflow Generator HTTP Methods Reference HTTP Code Snippet Generator HTTP Status Code Reference HTTP Status Code Reference JSON Diff Viewer JSON Diff package.json Validator & Audit Regex Visualizer Regex Playground Regex Tester Pro Regex Tester Dockerfile Generator Rate Limiter Tester API Changelog Generator HTTP Mock Response Builder Prometheus Alert Rules Validator HTTP Status Code Lookup Cron Expression Tester HTTP Status Code Lookup Vue Component Generator HTTP Status Code Reference CI/CD Pipeline Generator TypeScript tsconfig.json Generator LLM Context Window Calculator LLM Token Counter Kubernetes Resource Calculator k6 Load Test Script Generator NATS Connection String Builder Playwright E2E Test Generator Prompt Token Estimator Playwright E2E Testing Guide Pulumi Modern IaC Guide GitHub Actions Cron Builder HTML5 Canvas API Playground Prompt Injection Tester Token Context Calculator OpenTelemetry Trace Builder LangChain PromptTemplate Builder OpenAI Function Calling Generator Token Counter & Cost Estimator AI API Rate Limit Calculator

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$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.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →