Skip to content

Supertest for Node.js Deep Dive — Programmatic API Testing with Express

DodaTech Updated 2026-06-28 4 min read

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

  1. How does Supertest handle server lifecycle?
  2. How do you test authenticated endpoints?
  3. What is the difference between .expect() and manual assertions?
  4. How do you test file uploads with Supertest?
  5. How do you set up and tear down test databases?

Answers:

  1. Supertest creates an internal HTTP server from the app instance. It manages the server lifecycle automatically — no need to call app.listen().
  2. Set the Authorization header: .set('Authorization', 'Bearer token'). Generate test tokens in beforeAll using jwt.sign().
  3. .expect() is a Supertest chainable assertion that fails the test immediately. Manual assertions (expect().toBe()) give more control and better error messages.
  4. Use .attach('fieldname', Buffer.from('content'), 'filename.txt') for file fields. Set Content-Type to multipart/form-data.
  5. 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

Can Supertest test GraphQL APIs?

Yes. Send POST requests with GraphQL queries in the body. Use .send({ query: '{ threats { id name } }' }) and assert on response.data.

How do I test WebSocket endpoints?

Supertest is for HTTP only. Use the ws library or socket.io-client for WebSocket testing alongside Supertest for HTTP endpoints.

What is the difference between Supertest and Jest?

Supertest is an HTTP assertion library. Jest is a test runner and assertion framework. They are used together — Supertest with Jest or Mocha.

Can Supertest handle multipart forms?

Yes. Use .field('key', 'value') for form fields and .attach('file', path) for file uploads. Supertest sets the correct Content-Type automatically.

How do I test error middleware?

Trigger errors by sending invalid data, missing fields, or malformed requests. Assert the error status code and error response structure.

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