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.