How do you integration-test an Express API (e.g., with supertest)?
3 minintermediatenodejsexpresssupertestintegration-testingapi
Quick Answer
Use supertest to send real HTTP requests to your Express app object (no need to bind a port) and assert on status, headers, and body. Run middleware, routing, and handlers together against a test database so you verify the actual request/response path end-to-end within the service.
Detailed Answer
Answer:
Integration tests exercise the real request pipeline — middleware, routing, handlers, and (ideally) a test database — through HTTP. supertest drives your Express app directly.
Export the app separately from the server so tests can import it without listening on a port:
// app.js — build and export the app
const app = express();
app.use(express.json());
app.use('/users', usersRouter);
module.exports = app;
// server.js — only this file calls listen()
require('./app').listen(3000);
Test with supertest:
const request = require('supertest');
const app = require('./app');
describe('POST /users', () => {
test('creates a user and returns 201', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Alice', email: 'alice@x.com' })
.expect('Content-Type', /json/)
.expect(201);
expect(res.body).toMatchObject({ name: 'Alice' });
expect(res.body.id).toBeDefined();
});
test('rejects invalid input with 400', async () => {
await request(app).post('/users').send({}).expect(400);
});
});
Good practices:
- Use a dedicated test database (or an in-memory/containerized one), reset between tests (
beforeEachtruncate, or wrap each test in a rolled-back transaction). - Test the contract: status codes, response shape, validation errors, auth (send/omit tokens), and edge cases — not just the happy path.
- Mock only truly external third parties (payment gateways, email); keep your own DB real so you catch query/serialization bugs.
- supertest calls
appin-process (it starts an ephemeral server), so no port management or flakiness from a separately running server.