19 Testing Jest Supertest
title: Testing with Jest and Supertest in Node.js REST APIs weight: 29 date: 2026-06-28 lastmod: 2026-06-28 description: Master testing Node.js REST APIs with Jest and Supertest including unit tests, integration tests, test database setup, mocking, and CI pipeline integration. tags: [api-development, nodejs]
Testing Node.js REST APIs with Jest and Supertest validates endpoint behavior by sending HTTP requests and asserting responses, covering unit tests for services, integration tests for endpoints, and test database management.
```mermaid
flowchart TD
A[Tests] --> B[Unit Tests]
A --> C[Integration Tests]
A --> D[E2E Tests]
B --> E[Test services]
B --> F[Mock database]
C --> G[Test endpoints]
C --> H[Test database]
D --> I[Full system test]
style A fill:#e1f5fe
style B fill:#c8e6c9
style C fill:#c8e6c9
Jest is the test runner and assertion library. Supertest provides an HTTP assertion library that starts the Express app and makes test requests without binding to a port. Unit tests isolate services with mocked dependencies. Integration tests use a test database.
Think of testing like a quality control line in a factory. Unit tests check individual components (one screw). Integration tests check assembled parts (the dashboard). E2E tests check the finished product (the car driving).
Example: Unit Test for Service
const userService = require('../services/userService');
const User = require('../models/User');
jest.mock('../models/User');
describe('UserService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('findById returns user when found', async () => {
const mockUser = { id: '123', name: 'Alice', email: 'alice@test.com' };
User.findById.mockResolvedValue(mockUser);
const result = await userService.findById('123');
expect(result).toEqual(mockUser);
expect(User.findById).toHaveBeenCalledWith('123');
});
test('findById returns null when user not found', async () => {
User.findById.mockResolvedValue(null);
const result = await userService.findById('999');
expect(result).toBeNull();
});
});
Expected output:
PASS tests/unit/userService.test.js
UserService
✓ findById returns user when found (3 ms)
✓ findById returns null when user not found (1 ms)
Example: Integration Test for Endpoints
const request = require('supertest');
const app = require('../app');
const mongoose = require('mongoose');
const User = require('../models/User');
beforeAll(async () => {
await mongoose.connect(process.env.TEST_DATABASE_URL);
});
afterEach(async () => {
await User.deleteMany({});
});
afterAll(async () => {
await mongoose.disconnect();
});
describe('POST /api/users', () => {
test('creates a new user with valid data', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Test User', email: 'test@test.com', password: 'Password123' })
.expect(201);
expect(res.body.status).toBe('success');
expect(res.body.data.name).toBe('Test User');
expect(res.body.data.email).toBe('test@test.com');
expect(res.body.data).not.toHaveProperty('password');
});
test('returns 400 with invalid email', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Test', email: 'invalid', password: 'Pass123' })
.expect(400);
expect(res.body.status).toBe('error');
});
});
Expected output:
PASS tests/integration/user.test.js
POST /api/users
✓ creates a new user with valid data (45 ms)
✓ returns 400 with invalid email (12 ms)
Example: Mocking External Services
const request = require('supertest');
const app = require('../app');
jest.mock('../services/emailService', () => ({
sendWelcomeEmail: jest.fn().mockResolvedValue(true)
}));
describe('User Registration with Email', () => {
test('sends welcome email after registration', async () => {
const emailService = require('../services/emailService');
await request(app)
.post('/api/users/register')
.send({ name: 'Alice', email: 'alice@test.com', password: 'Pass123' })
.expect(201);
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('alice@test.com');
});
});
Common Mistakes
- Not using a separate test database — Running tests against the development database corrupts data and makes tests flaky. Use a dedicated test database or in-memory database.
- Testing implementation details instead of behavior — Test what the API does (response status, body), not how it does it (which service was called).
- Forgetting to clean up test data — Test data persists between tests and causes false failures. Clean up after each test with afterEach hooks.
- Not testing error scenarios — Happy path tests are not enough. Test 400, 401, 403, 404, and 500 responses with invalid inputs.
- Flaky tests due to shared state — Tests that depend on the order of execution or shared global state are unreliable. Each test should be independent.
Practice Questions
- What is the difference between unit and integration tests?
- How does Supertest make HTTP assertions without starting a server?
- Why should you use a separate test database?
- How do you mock external services in Jest?
- Challenge: Write a complete test suite for a blog API. Include unit tests for all services (post, comment, user), integration tests for all CRUD endpoints, authentication tests, and tests for error scenarios. Achieve at least 80% code coverage.
FAQ
Mini Project
Write a comprehensive test suite for a todo list API. Include: unit tests for all services (todos, users), integration tests for all endpoints (CRUD, auth, search), test database setup with mongodb-memory-server, mocked email service, authentication tests, error scenario tests, and a CI configuration that runs tests on push.
What's Next
Now learn about logging with Winston and Pino in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro