How do you know that a change you made didn't break an existing API endpoint? By writing automated tests! API testing involves making requests to your API endpoints and asserting that they return the correct status codes, headers, and body content.

The most popular library for this in the Node.js world is Supertest. It provides a high-level abstraction for testing HTTP, allowing you to chain assertions in a fluent, readable way. We'll use it with the Jest test runner.

1. Setting Up the Test Environment

First, install the necessary development dependencies:

Bash


npm install --save-dev jest supertest

Next, configure Jest by adding a test script to your package.json:

JSON


{
  "scripts": {
    "test": "jest"
  }
}

2. Structuring Your App for Testability

To allow Supertest to "listen" to your app without actually starting a server on a real port, it's best practice to separate your app creation from your app.listen() call.

Original app.js:

JavaScript


const express = require('express');
const app = express();
// ... routes ...
app.listen(3000);

Refactored app.js (testable):

JavaScript


const express = require('express');
const app = express();
app.use(express.json());

// --- Routes ---
app.get('/hello', (req, res) => {
  res.status(200).json({ message: 'world' });
});

app.post('/echo', (req, res) => {
  res.status(201).json({ youSent: req.body });
});

// Export the app for testing
module.exports = app;

New server.js (to start the app):

JavaScript


const app = require('./app'); // Import the configured app
const port = 3000;

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

This separation allows us to import the app object directly into our test files.

3. Writing Your First API Test

Create a test file, for example, app.test.js.

JavaScript


// app.test.js
const request = require('supertest');
const app = require('./app'); // Import the app object

// The describe block groups tests for a specific part of your application
describe('GET /hello endpoint', () => {

  // The 'it' or 'test' block is an individual test case
  it('should return a 200 OK status code', async () => {
    const response = await request(app).get('/hello');
    // 'expect' is from Jest, used for assertions
    expect(response.statusCode).toBe(200);
  });

  it('should return a JSON object with message: "world"', async () => {
    const response = await request(app).get('/hello');
    // Supertest automatically parses the JSON response body
    expect(response.body).toEqual({ message: 'world' });
  });
});


describe('POST /echo endpoint', () => {
  it('should echo back the request body', async () => {
    const testData = { name: 'Jest', type: 'testing' };

    const response = await request(app)
      .post('/echo')
      .send(testData); // Send data in the request body
    
    expect(response.statusCode).toBe(201);
    expect(response.body).toEqual({ youSent: testData });
  });
});

To run the tests, simply execute:

Bash


npm test

Jest will discover and run the test file, and Supertest will make actual HTTP requests against your Express app instance. It will report whether your endpoints behaved exactly as you asserted, providing a powerful safety net against regressions.