Jest and Supertest let you test your Node.js REST APIs by actually sending HTTP requests to your application and asserting the responses.
Let’s say you have a simple Express app:
// app.js
const express = require('express');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
let nextId = 3;
app.get('/users', (req, res) => {
res.json(users);
});
app.post('/users', (req, res) => {
const newUser = { id: nextId++, name: req.body.name };
users.push(newUser);
res.status(201).json(newUser);
});
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (user) {
res.json(user);
} else {
res.status(404).send('User not found');
}
});
module.exports = app;
Now, set up your test file (users.test.js):
// users.test.js
const request = require('supertest');
const app = require('./app'); // Import your Express app
describe('User API', () => {
it('should get all users', async () => {
const response = await request(app).get('/users');
expect(response.statusCode).toBe(200);
expect(response.body).toHaveLength(2);
expect(response.body[0]).toHaveProperty('id', 1);
expect(response.body[0]).toHaveProperty('name', 'Alice');
});
it('should create a new user', async () => {
const newUser = { name: 'Charlie' };
const response = await request(app)
.post('/users')
.send(newUser);
expect(response.statusCode).toBe(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('Charlie');
// Verify it was added
const getResponse = await request(app).get('/users');
expect(getResponse.body).toHaveLength(3);
});
it('should get a specific user by ID', async () => {
const response = await request(app).get('/users/1');
expect(response.statusCode).toBe(200);
expect(response.body).toHaveProperty('id', 1);
expect(response.body).toHaveProperty('name', 'Alice');
});
it('should return 404 for non-existent user', async () => {
const response = await request(app).get('/users/99');
expect(response.statusCode).toBe(404);
expect(response.text).toBe('User not found');
});
});
To run these tests, you’ll need Jest and Supertest installed:
npm install --save-dev jest supertest
And add a test script to your package.json:
"scripts": {
"test": "jest"
}
Then run npm test.
Supertest works by taking your application instance (in this case, the Express app object) and creating a "request agent" that can make HTTP requests to it without needing a running server. Jest then provides the testing framework, assertion library (expect), and test runner.
The request(app) part is key. It allows Supertest to bypass the network stack and directly invoke your application’s request handlers. This makes tests fast and reliable because they aren’t subject to network latency or external dependencies. request(app).get('/users') simulates sending a GET request to /users against your app instance. The .send(newUser) method is used for POST, PUT, or PATCH requests to include a request body.
The .body property on the response object is populated by Supertest automatically if the Content-Type is application/json. This saves you from manually parsing JSON. Assertions like expect(response.statusCode).toBe(200) and expect(response.body).toHaveLength(2) are standard Jest assertions applied to the test results.
You can also test error handling. For instance, if your API has authentication middleware, you’d use .set('Authorization', 'Bearer invalid_token') before sending the request to simulate an unauthorized access attempt and assert the expected 401 status code.
The most surprising thing about Supertest is how it elegantly handles asynchronous operations. Each request() call returns a Promise, so you can simply await the response, making your test code look remarkably like synchronous code while still being non-blocking.
When you’re testing more complex scenarios, consider using beforeEach and afterEach blocks in Jest to set up or tear down test data. For instance, if your API modifies a database, you might reset the database to a known state before each test to ensure isolation.
The next challenge is testing WebSocket connections alongside your REST endpoints.