API Testing
Learn to test APIs using Jest, Supertest, and other testing tools. This is a foundational concept in server-side JavaScript development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Node.js experience. Take your time with each section and practice the examples
API Testing with Jest and Supertest
API testing is crucial for ensuring your endpoints work correctly and handle various scenarios properly. Jest and Supertest are popular tools for testing Node.js APIs.. This is an essential concept that every Node.js developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
API Testing Implementation
// Install testing dependencies
npm install --save-dev jest supertest
// test/users.test.js
const request = require('supertest');
const app = require('../app');
const mongoose = require('mongoose');
const User = require('../models/User');
describe('User API', () => {
beforeAll(async () => {
// Connect to test database
await mongoose.connect(process.env.MONGODB_TEST_URI);
});
afterAll(async () => {
// Clean up and disconnect
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
});
beforeEach(async () => {
// Clear users collection before each test
await User.deleteMany({});
});
describe('GET /api/users', () => {
it('should return empty array when no users exist', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toEqual([]);
expect(response.body.count).toBe(0);
});
it('should return all users', async () => {
// Create test users
const user1 = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const user2 = await User.create({
name: 'Jane Smith',
email: 'jane@example.com',
age: 25
});
const response = await request(app)
.get('/api/users')
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveLength(2);
expect(response.body.count).toBe(2);
});
it('should support pagination', async () => {
// Create multiple users
const users = [];
for (let i = 0; i < 15; i++) {
users.push({
name: `User ${i}`,
email: `user${i}@example.com`,
age: 20 + i
});
}
await User.insertMany(users);
const response = await request(app)
.get('/api/users?page=1&limit=10')
.expect(200);
expect(response.body.data).toHaveLength(10);
expect(response.body.pagination.page).toBe(1);
expect(response.body.pagination.limit).toBe(10);
expect(response.body.pagination.total).toBe(15);
expect(response.body.pagination.pages).toBe(2);
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data.name).toBe(userData.name);
expect(response.body.data.email).toBe(userData.email);
expect(response.body.data.age).toBe(userData.age);
});
it('should return 400 for invalid data', async () => {
const invalidData = {
name: '', // Empty name
email: 'invalid-email', // Invalid email
age: -5 // Invalid age
};
const response = await request(app)
.post('/api/users')
.send(invalidData)
.expect(400);
expect(response.body.success).toBe(false);
});
});
describe('GET /api/users/:id', () => {
it('should return user by ID', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data._id).toBe(user._id.toString());
});
it('should return 404 for non-existent user', async () => {
const fakeId = new mongoose.Types.ObjectId();
const response = await request(app)
.get(`/api/users/${fakeId}`)
.expect(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('User not found');
});
});
describe('PUT /api/users/:id', () => {
it('should update user', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const updateData = {
name: 'John Updated',
age: 31
};
const response = await request(app)
.put(`/api/users/${user._id}`)
.send(updateData)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.data.name).toBe(updateData.name);
expect(response.body.data.age).toBe(updateData.age);
});
});
describe('DELETE /api/users/:id', () => {
it('should delete user', async () => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const response = await request(app)
.delete(`/api/users/${user._id}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.message).toBe('User deleted successfully');
// Verify user is deleted
const deletedUser = await User.findById(user._id);
expect(deletedUser).toBeNull();
});
});
});
// jest.config.js
module.exports = {
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
collectCoverageFrom: [
'routes/**/*.js',
'models/**/*.js',
'middleware/**/*.js'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html']
};