Supertest for Node.js Deep Dive — Programmatic API Testing with Express
In this tutorial, you will learn about Supertest for Node.js Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
Supertest provides a high-level abstraction for testing HTTP APIs in Node.js, allowing you to make requests to an Express (or any HTTP) application and assert on the response programmatically.
Code Example: Basic Supertest Tests
const request = require('supertest');
const app = require('../src/app');
describe('GET /api/v1/threats', () => {
it('should return list of threats', async () => {
const res = await request(app)
.get('/api/v1/threats')
.set('Authorization', 'Bearer test-token')
.expect('Content-Type', /json/)
.expect(200);
expect(res.body).toHaveProperty('threats');
expect(Array.isArray(res.body.threats)).toBe(true);
});
it('should support pagination', async () => {
const res = await request(app)
.get('/api/v1/threats')
.query({ page: 1, page_size: 10 })
.set('Authorization', 'Bearer test-token')
.expect(200);
expect(res.body.page).toBe(1);
expect(res.body.threats.length).toBeLessThanOrEqual(10);
});
it('should return 401 without auth', async () => {
await request(app)
.get('/api/v1/threats')
.expect(401);
});
});
Code Example: Testing Request Body and Response
describe('POST /api/v1/threats', () => {
const validThreat = {
name: 'SQL Injection Attempt',
severity: 'high',
source_ip: '192.168.1.100',
description: 'SQL injection detected in login form'
};
it('should create a threat with valid data', async () => {
const res = await request(app)
.post('/api/v1/threats')
.send(validThreat)
.set('Authorization', 'Bearer test-token')
.expect(201);
expect(res.body).toMatchObject({
name: validThreat.name,
severity: validThreat.severity,
source_ip: validThreat.source_ip
});
expect(res.body).toHaveProperty('id');
expect(res.body).toHaveProperty('created_at');
});
it('should validate required fields', async () => {
const res = await request(app)
.post('/api/v1/threats')
.send({ name: 'Incomplete' })
.set('Authorization', 'Bearer test-token')
.expect(422);
expect(res.body.error).toBe('VALIDATION_ERROR');
expect(res.body.details.length).toBeGreaterThan(0);
});
it('should reject invalid severity values', async () => {
const res = await request(app)
.post('/api/v1/threats')
.send({ ...validThreat, severity: 'ultra-critical' })
.set('Authorization', 'Bearer test-token')
.expect(422);
});
});
Code Example: Database Integration with Supertest
const { MongoMemoryServer } = require('mongodb-memory-server');
const mongoose = require('mongoose');
const app = require('../src/app');
describe('Threat API with Database', () => {
let mongoServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
await mongoose.connect(mongoServer.getUri());
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
beforeEach(async () => {
await mongoose.connection.db.dropDatabase();
});
it('should persist created threat to database', async () => {
const createRes = await request(app)
.post('/api/v1/threats')
.send({
name: 'Persistent Threat',
severity: 'medium',
source_ip: '10.0.0.1'
})
.set('Authorization', 'Bearer test-token')
.expect(201);
const threatId = createRes.body.id;
// Fetch from database directly
const dbThreat = await ThreatModel.findById(threatId);
expect(dbThreat).not.toBeNull();
expect(dbThreat.name).toBe('Persistent Threat');
expect(dbThreat.severity).toBe('medium');
});
it('should update threat in database', async () => {
const threat = await ThreatModel.create({
name: 'Update Test',
severity: 'low',
source_ip: '10.0.0.2'
});
await request(app)
.patch(`/api/v1/threats/${threat._id}`)
.send({ severity: 'high' })
.set('Authorization', 'Bearer test-token')
.expect(200);
const updated = await ThreatModel.findById(threat._id);
expect(updated.severity).toBe('high');
});
});
Common Mistakes
1. Not Awaiting Supertest Calls
Supertest returns a Promise. Without await, the test completes before the response is received, causing false passes.
2. Using app.listen Instead of app
Pass the Express app instance (not the listening server) to Supertest. Supertest handles server lifecycle internally.
3. Not Cleaning Database Between Tests
Test data persists across tests. Use beforeEach to clean the database or wrap tests in transactions that roll back.
4. Hardcoding Auth Tokens
Generate tokens dynamically in beforeAll using the same JWT library as the application. Hardcoded tokens expire.
5. Ignoring Response Headers
Status codes and body are validated, but headers (Content-Type, rate limits, CORS) are often forgotten. Assert on headers too.
Practice Questions
- How does Supertest handle server lifecycle?
- How do you test authenticated endpoints?
- What is the difference between .expect() and manual assertions?
- How do you test file uploads with Supertest?
- How do you set up and tear down test databases?
Answers:
- Supertest creates an internal HTTP server from the app instance. It manages the server lifecycle automatically — no need to call app.listen().
- Set the Authorization header: .set('Authorization', 'Bearer token'). Generate test tokens in beforeAll using jwt.sign().
- .expect() is a Supertest chainable assertion that fails the test immediately. Manual assertions (expect().toBe()) give more control and better error messages.
- Use .attach('fieldname', Buffer.from('content'), 'filename.txt') for file fields. Set Content-Type to multipart/form-data.
- Use in-memory databases (MongoMemoryServer, SQLite :memory:) for fast isolated tests. Use beforeEach to clean data between tests.
Challenge: Build a complete Supertest test suite for an Express threat intelligence API with in-memory database, authentication, CRUD operations, validation, error handling, and database persistence verification.
FAQ
Mini Project
Build a Supertest test suite for an Express threat intelligence API with: in-memory MongoDB for database testing, JWT token generation for auth, CRUD endpoint tests, input validation tests, error handling tests, and database persistence verification.
What's Next
Now learn about Supertest Assertions with Expect/Should for advanced response validation patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro