Comprehensive API testing is essential for reliable frontend applications. This guide covers tools like Postman, Hoppscotch, Insomnia, and automated testing strategies for API endpoints.
# CLI tools
npm install -g newman # Postman CLI
npm install -g hoppscotch-cli
# Testing libraries
npm install --save-dev axios supertest jest
npm install --save-dev @testing-library/react @testing-library/jest-dom// test-setup.js
import '@testing-library/jest-dom';
// Mock fetch globally
global.fetch = jest.fn();
// Setup test environment
beforeEach(() => {
fetch.mockClear();
});{
"info": {
"name": "User API Tests",
"description": "Comprehensive tests for user management API"
},
"item": [
{
"name": "Authentication",
"item": [
{
"name": "Login",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"test@example.com\",\n \"password\": \"password123\"\n}"
},
"url": {
"raw": "{{baseUrl}}/auth/login",
"host": ["{{baseUrl}}"],
"path": ["auth", "login"]
}
},
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response has token\", function () {",
" const jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('token');",
" pm.environment.set('authToken', jsonData.token);",
"});",
"",
"pm.test(\"Response time is less than 1000ms\", function () {",
" pm.expect(pm.response.responseTime).to.be.below(1000);",
"});"
]
}
}
]
}
]
}
]
}{
"id": "api-env",
"name": "API Environment",
"values": [
{
"key": "baseUrl",
"value": "https://api.example.com",
"enabled": true
},
{
"key": "authToken",
"value": "",
"enabled": true
},
{
"key": "userId",
"value": "",
"enabled": true
}
]
}// Generate dynamic data
const timestamp = Date.now();
pm.environment.set('timestamp', timestamp);
pm.environment.set('randomEmail', `test${timestamp}@example.com`);
// Set authentication headers
const token = pm.environment.get('authToken');
if (token) {
pm.request.headers.add({
key: 'Authorization',
value: `Bearer ${token}`
});
}
// Add request ID for tracking
pm.request.headers.add({
key: 'X-Request-ID',
value: pm.variables.replaceIn('{{$randomUUID}}')
});// Response validation
pm.test("Response is JSON", function () {
pm.response.to.be.json;
});
pm.test("Response has required fields", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('id');
pm.expect(jsonData).to.have.property('name');
pm.expect(jsonData).to.have.property('email');
});
pm.test("Response time is acceptable", function () {
pm.expect(pm.response.responseTime).to.be.below(2000);
});
pm.test("Response headers are correct", function () {
pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
});
// Data type validation
pm.test("ID is a number", function () {
const jsonData = pm.response.json();
pm.expect(jsonData.id).to.be.a('number');
});
pm.test("Email format is valid", function () {
const jsonData = pm.response.json();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
pm.expect(jsonData.email).to.match(emailRegex);
});
// Status code validation
pm.test("Success status code", function () {
pm.expect(pm.response.code).to.be.oneOf([200, 201]);
});{
"v": 1,
"name": "User API Collection",
"folders": [
{
"name": "Authentication",
"requests": [
{
"name": "Login",
"method": "POST",
"endpoint": "{{baseUrl}}/auth/login",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"contentType": "application/json",
"body": "{\n \"email\": \"{{testEmail}}\",\n \"password\": \"{{testPassword}}\"\n}"
},
"tests": "tests.login"
}
]
}
]
}// tests/login.js
pw.test("Login successful", function () {
pw.expect(pw.response.status).toBe(200);
});
pw.test("Response has token", function () {
const response = pw.response.json();
pw.expect(response).toHaveProperty("token");
pw.environment.set("authToken", response.token);
});
pw.test("Response time under 1 second", function () {
pw.expect(pw.response.responseTime).toBeLessThan(1000);
});// tests/api/user.test.js
const axios = require('axios');
const API_BASE = process.env.API_BASE || 'http://localhost:3000/api';
describe('User API', () => {
let authToken;
let userId;
beforeAll(async () => {
// Setup test data
const loginResponse = await axios.post(`${API_BASE}/auth/login`, {
email: 'test@example.com',
password: 'password123'
});
authToken = loginResponse.data.token;
});
describe('GET /users', () => {
it('should return list of users', async () => {
const response = await axios.get(`${API_BASE}/users`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
expect(Array.isArray(response.data)).toBe(true);
expect(response.data.length).toBeGreaterThan(0);
});
it('should support pagination', async () => {
const response = await axios.get(`${API_BASE}/users?page=1&limit=10`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
expect(response.data.length).toBeLessThanOrEqual(10);
});
it('should filter users by query', async () => {
const response = await axios.get(`${API_BASE}/users?search=john`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
expect(response.data.every(user =>
user.name.toLowerCase().includes('john')
)).toBe(true);
});
});
describe('POST /users', () => {
it('should create a new user', async () => {
const userData = {
name: 'Test User',
email: `test${Date.now()}@example.com`,
password: 'password123'
};
const response = await axios.post(`${API_BASE}/users`, userData, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(201);
expect(response.data).toHaveProperty('id');
expect(response.data.name).toBe(userData.name);
expect(response.data.email).toBe(userData.email);
userId = response.data.id;
});
it('should validate required fields', async () => {
const response = await axios.post(`${API_BASE}/users`, {}, {
headers: { Authorization: `Bearer ${authToken}` },
validateStatus: () => true
});
expect(response.status).toBe(400);
expect(response.data).toHaveProperty('errors');
});
});
describe('PUT /users/:id', () => {
it('should update user', async () => {
const updateData = { name: 'Updated Name' };
const response = await axios.put(`${API_BASE}/users/${userId}`, updateData, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
expect(response.data.name).toBe(updateData.name);
});
it('should return 404 for non-existent user', async () => {
const response = await axios.put(`${API_BASE}/users/99999`, { name: 'Test' }, {
headers: { Authorization: `Bearer ${authToken}` },
validateStatus: () => true
});
expect(response.status).toBe(404);
});
});
describe('DELETE /users/:id', () => {
it('should delete user', async () => {
const response = await axios.delete(`${API_BASE}/users/${userId}`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(204);
});
});
});// tests/api/supertest.test.js
const request = require('supertest');
const app = require('../../app');
describe('API Endpoints', () => {
let authToken;
beforeAll(async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'password123'
});
authToken = response.body.token;
});
describe('User Management', () => {
it('should create and retrieve user', async () => {
const userData = {
name: 'Test User',
email: `test${Date.now()}@example.com`,
password: 'password123'
};
// Create user
const createResponse = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.send(userData)
.expect(201);
const userId = createResponse.body.id;
// Retrieve user
const getResponse = await request(app)
.get(`/api/users/${userId}`)
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(getResponse.body.name).toBe(userData.name);
expect(getResponse.body.email).toBe(userData.email);
});
it('should handle validation errors', async () => {
await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Test' }) // Missing required fields
.expect(400)
.expect((res) => {
expect(res.body.errors).toBeDefined();
expect(res.body.errors.email).toBeDefined();
});
});
});
});# artillery-config.yml
config:
target: 'https://api.example.com'
phases:
- duration: 60
arrivalRate: 10
- duration: 120
arrivalRate: 20
- duration: 60
arrivalRate: 10
scenarios:
- name: "User API Load Test"
weight: 100
flow:
- post:
url: "/auth/login"
json:
email: "test@example.com"
password: "password123"
capture:
- json: "$.token"
as: "authToken"
- get:
url: "/users"
headers:
Authorization: "Bearer {{ authToken }}"
- post:
url: "/users"
headers:
Authorization: "Bearer {{ authToken }}"
json:
name: "Load Test User"
email: "loadtest{{ $randomInt(1, 10000) }}@example.com"
password: "password123"// tests/performance/api.test.js
const axios = require('axios');
describe('API Performance Tests', () => {
const API_BASE = process.env.API_BASE || 'http://localhost:3000/api';
const PERFORMANCE_THRESHOLD = 1000; // 1 second
it('should respond within performance threshold', async () => {
const startTime = Date.now();
const response = await axios.get(`${API_BASE}/users`);
const responseTime = Date.now() - startTime;
expect(response.status).toBe(200);
expect(responseTime).toBeLessThan(PERFORMANCE_THRESHOLD);
});
it('should handle concurrent requests', async () => {
const concurrentRequests = 10;
const requests = Array(concurrentRequests).fill().map(() =>
axios.get(`${API_BASE}/users`)
);
const startTime = Date.now();
const responses = await Promise.all(requests);
const totalTime = Date.now() - startTime;
expect(responses.every(res => res.status === 200)).toBe(true);
expect(totalTime).toBeLessThan(PERFORMANCE_THRESHOLD * 2);
});
});// tests/mocks/handlers.js
import { rest } from 'msw';
export const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
])
);
}),
rest.post('/api/users', (req, res, ctx) => {
const { name, email } = req.body;
return res(
ctx.status(201),
ctx.json({
id: Date.now(),
name,
email,
createdAt: new Date().toISOString()
})
);
}),
rest.put('/api/users/:id', (req, res, ctx) => {
const { id } = req.params;
const { name, email } = req.body;
return res(
ctx.status(200),
ctx.json({
id: parseInt(id),
name,
email,
updatedAt: new Date().toISOString()
})
);
}),
rest.delete('/api/users/:id', (req, res, ctx) => {
return res(ctx.status(204));
})
];// tests/setup.js
import { setupServer } from 'msw/node';
import { handlers } from './mocks/handlers';
export const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());// tests/fixtures/users.js
export const testUsers = {
validUser: {
name: 'Test User',
email: 'test@example.com',
password: 'password123'
},
invalidUser: {
name: 'Test',
email: 'invalid-email',
password: '123'
},
adminUser: {
name: 'Admin User',
email: 'admin@example.com',
password: 'admin123',
role: 'admin'
}
};
// tests/helpers/testData.js
export function generateTestUser(overrides = {}) {
return {
name: 'Test User',
email: `test${Date.now()}@example.com`,
password: 'password123',
...overrides
};
}// tests/api/error-handling.test.js
describe('Error Handling', () => {
it('should handle network errors gracefully', async () => {
// Mock network error
axios.interceptors.request.use(() => {
throw new Error('Network Error');
});
try {
await axios.get(`${API_BASE}/users`);
} catch (error) {
expect(error.message).toBe('Network Error');
}
});
it('should handle server errors', async () => {
const response = await axios.get(`${API_BASE}/users`, {
validateStatus: () => true
});
if (response.status >= 500) {
expect(response.data).toHaveProperty('error');
}
});
});// tests/api/security.test.js
describe('Security Tests', () => {
it('should require authentication', async () => {
const response = await axios.get(`${API_BASE}/users`, {
validateStatus: () => true
});
expect(response.status).toBe(401);
});
it('should validate JWT tokens', async () => {
const response = await axios.get(`${API_BASE}/users`, {
headers: { Authorization: 'Bearer invalid-token' },
validateStatus: () => true
});
expect(response.status).toBe(401);
});
it('should prevent SQL injection', async () => {
const maliciousQuery = "'; DROP TABLE users; --";
const response = await axios.get(`${API_BASE}/users?search=${maliciousQuery}`, {
validateStatus: () => true
});
// Should not cause server error
expect(response.status).not.toBe(500);
});
});