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-jestApp 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
| Approach | Speed | Isolation | Realism |
|---|---|---|---|
| Mock service layer | Fastest | High | Low |
| In-memory DB (SQLite) | Fast | Medium | Medium |
| Test database | Slower | Low (needs cleanup) | High |
| Docker container | Slowest | High | Highest |
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 readyPremium
A concise answer to help you respond confidently on this topic during an interview.