Skip to main content
Practice Problems

How to test an Express.js application?

Testing Express.js Applications

Testing Express apps involves unit tests for individual functions and integration tests for the full HTTP request/response cycle. The key tool is Supertest — it makes real HTTP requests to your app without starting a server.


Setup

bash
npm install --save-dev jest supertest # or with TypeScript: npm install --save-dev jest supertest @types/jest @types/supertest ts-jest

App Structure for Testing

Separate your Express app from the server start:

js
// app.js — export app, no listen() const express = require('express'); const app = express(); app.use(express.json()); app.use('/users', usersRouter); module.exports = app; // server.js — only this file calls listen() const app = require('./app'); app.listen(3000);

Integration Tests with Supertest

js
// users.test.js const request = require('supertest'); const app = require('../app'); describe('Users API', () => { describe('GET /users', () => { it('should return 200 and array of users', async () => { const res = await request(app).get('/users'); expect(res.status).toBe(200); expect(res.body).toHaveProperty('data'); expect(Array.isArray(res.body.data)).toBe(true); }); }); describe('POST /users', () => { it('should create a user and return 201', async () => { const res = await request(app) .post('/users') .send({ name: 'Alice', email: 'alice@test.com' }); expect(res.status).toBe(201); expect(res.body.data.name).toBe('Alice'); }); it('should return 400 when email is missing', async () => { const res = await request(app) .post('/users') .send({ name: 'Alice' }); // no email! expect(res.status).toBe(400); expect(res.body).toHaveProperty('error'); }); }); describe('GET /users/:id', () => { it('should return 404 for non-existent user', async () => { const res = await request(app).get('/users/9999'); expect(res.status).toBe(404); }); }); });

Testing Protected Routes

js
const jwt = require('jsonwebtoken'); function generateTestToken(payload = { id: 1, role: 'user' }) { return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' }); } describe('Protected Routes', () => { it('should return 401 without token', async () => { const res = await request(app).get('/profile'); expect(res.status).toBe(401); }); it('should return 200 with valid token', async () => { const token = generateTestToken(); const res = await request(app) .get('/profile') .set('Authorization', `Bearer ${token}`); expect(res.status).toBe(200); }); });

Mocking Services

js
// Mock the database layer jest.mock('../services/users.service', () => ({ findAll: jest.fn().mockResolvedValue([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]), findById: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }), create: jest.fn().mockResolvedValue({ id: 3, name: 'Charlie' }) })); const usersService = require('../services/users.service'); it('should call service.findAll once', async () => { await request(app).get('/users'); expect(usersService.findAll).toHaveBeenCalledTimes(1); });

Test Database Strategy

ApproachSpeedIsolationRealism
Mock service layerFastestHighLow
In-memory DB (SQLite)FastMediumMedium
Test databaseSlowerLow (needs cleanup)High
Docker containerSlowestHighHighest

jest.config.js

js
module.exports = { testEnvironment: 'node', setupFilesAfterEach: ['./jest.setup.js'], coveragePathIgnorePatterns: ['/node_modules/'], };

Summary

Use Supertest + Jest for testing Express apps. Keep your app in app.js (no listen()) so tests can import it directly. Test the full HTTP cycle: status codes, response body, headers, and authentication. Mock the database/service layer for fast, isolated unit tests.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems