Writing API Tests in Node.js With Jest and supertest
Testing your API catches bugs before users do. Here is how to use Jest and supertest.
Writing API Tests in Node.js With Jest and supertest
API tests catch bugs before users do. Here is how to write them with Jest and supertest.
Install
npm install --save-dev jest supertest. Add a test script: "test": "jest".
Set Up a Test Database
Use a separate MongoDB database for tests. Set MONGODB_URI in your test env to something like mongodb://localhost/test. Clean the database between tests.
The Basic Test
const request = require('supertest');
const app = require('../src/app');
const User = require('../src/models/user');
describe('POST /signup', () => {
beforeEach(async () => await User.deleteMany({}));
it('creates a user and returns 201', async () => {
const res = await request(app)
.post('/signup')
.send({ firstName: 'K', email: '[email protected]', password: 'password123' });
expect(res.status).toBe(201);
expect(res.body.id).toBeDefined();
expect(res.body.passwordHash).toBeUndefined();
});
it('returns 400 for missing fields', async () => {
const res = await request(app)
.post('/signup')
.send({ email: '[email protected]' });
expect(res.status).toBe(400);
});
});
What to Test
- Happy path: the endpoint works with valid input.
- Validation errors: missing fields, wrong types, bad formats.
- Auth errors: missing or invalid token returns 401.
- Authorization errors: wrong role returns 403.
- Not found: missing resource returns 404.
- Conflict: duplicate resource returns 409.
- Pagination and filtering: list endpoints behave correctly.
Tips
- Use a fresh database or clean between tests so they are independent.
- Test status codes AND response shapes (e.g., body.id is defined).
- Do not test internal implementation; test the API contract.
- Use beforeAll and afterAll for setup and teardown.
- Run tests in CI on every push.
Auth Tests
it('returns 401 without token', async () => {
const res = await request(app).get('/me');
expect(res.status).toBe(401);
});
it('returns 200 with valid token', async () => {
const login = await request(app)
.post('/login')
.send({ email: '[email protected]', password: 'password123' });
const token = login.body.token;
const res = await request(app)
.get('/me')
.set('Cookie', [`token=${token}`]);
expect(res.status).toBe(200);
});
The Takeaway
Test Express APIs with Jest and supertest. Use a test database. Test happy paths and error paths. Test status codes and response shapes. Do not test internal implementation; test the API contract. Run tests in CI.
Use Jest and supertest. Import the Express app, send requests with supertest, assert on the response. Test happy paths and error paths (validation, auth, not found, conflict). Use a test database and clean between tests.
So tests do not pollute dev or prod data. Set MONGODB_URI to a test database in your test env. Clean it between tests (User.deleteMany({})) so tests are independent and reproducible.
Happy path (valid input works), validation errors (missing or bad fields return 400), auth errors (missing or invalid token returns 401), authorization (wrong role returns 403), not found (404), and conflict (409). Test status codes AND response shapes.
Log in first to get a token. Then set the cookie or Authorization header on subsequent requests: request(app).get('/me').set('Cookie', [`token=${token}`]). Assert the status and body.
The API contract. Test that the endpoint returns the right status code and response shape for given inputs. Do not test internal function calls; those can change without changing the contract. Contract tests are more stable and useful.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

