typescript 95 lines · 4 tabs

Testing Express routes with Supertest + Jest

Shared by codesnips Jan 2026
4 tabs
import express, { Express, NextFunction, Request, Response } from 'express';
import { userRoutes } from './userRoutes';

function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.header('authorization');
  if (!header || !header.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'missing token' });
  }
  const token = header.slice('Bearer '.length);
  if (token !== 'valid-token') {
    return res.status(401).json({ error: 'invalid token' });
  }
  req.user = { id: '42', role: 'member' };
  next();
}

export function createApp(): Express {
  const app = express();
  app.use(express.json());
  app.use(authenticate);
  app.use('/users', userRoutes);
  return app;
}
4 files · typescript Explain with highlit

This snippet shows how an Express API is wired so its HTTP routes can be tested end-to-end with Supertest and Jest without spinning up a real network listener. The central idea is to separate the Express app from the process that binds it to a port. Supertest can take a bare app object and drive requests through it in-process, which makes tests fast, deterministic, and free of port collisions.

In app.ts the createApp factory returns a configured express() instance without ever calling listen. It mounts express.json(), an auth middleware, and a small /users router. Keeping construction in a factory means each test can build a fresh app, and it also isolates the app from server.ts, which is the only file that actually opens a socket. That split is the key trick for testable HTTP services.

userRoutes.ts defines the behavior under test. The GET /users/:id handler validates that req.user was populated by the auth layer, returns 403 when the caller asks for another user's record, and delegates to a userService that the tests will stub. Because the route depends on an injected-looking module rather than a live database, the test can control its return value and assert on both success and failure paths.

users.test.ts is the integration test. It uses jest.mock to replace the userService module so no real datastore is touched, and beforeEach resets mocks to prevent state leaking between cases. Each test calls request(app) from Supertest, sets an Authorization header, and chains .expect(...) assertions on status and body. The 403 case demonstrates testing an error branch by requesting a mismatched id, and the 404 case drives the stubbed service to return null.

The trade-off is that in-process tests exercise the full middleware stack and routing but not the real HTTP server or network, so a thin smoke test against server.ts is still worthwhile. Common pitfalls this structure avoids are leaking listeners between suites and forgetting to reset mocks. Reaching for this pattern is appropriate whenever route logic, middleware, and status codes need fast, reliable coverage.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Testing Express routes with Supertest + Jest — share card
Link copied