API Testing — Complete Guide to Validating Endpoints
In this tutorial, you will learn about API Testing. We cover key concepts, practical examples, and best practices to help you master this topic.
API testing validates that endpoints return correct responses under expected and unexpected conditions, covering functional tests, security tests, performance tests, and contract validation for every API.
What You'll Learn
- The four main categories of API testing
- How to write functional, security, and performance tests
- Tools and frameworks for automated API testing
Why It Matters
An untested API that fails in production breaks every consumer. Automated API testing catches regressions before deployment and ensures contracts are honored under all conditions.
Real-World Use
Doda Browser's bookmark sync API runs 500+ automated tests per deployment: functional tests verify each endpoint, security tests check authentication bypass attempts, and performance tests ensure under 200ms response time under load.
flowchart LR
A["API Testing"] --> B["Functional"]
A --> C["Security"]
A --> D["Performance"]
A --> E["Contract"]
B --> F["Status Codes"]
B --> G["Response Body"]
C --> H["Auth Bypass"]
C --> I["Injection"]
D --> J["Latency"]
D --> K["Throughput"]
style A fill:#dbeafe,stroke:#2563eb
Code Examples
import requests
import pytest
BASE = "https://api.example.com/v1"
def test_get_users_returns_200():
resp = requests.get(f"{BASE}/users", headers={"Authorization": "Bearer test-token"})
assert resp.status_code == 200
assert "users" in resp.json()
def test_get_users_requires_auth():
resp = requests.get(f"{BASE}/users")
assert resp.status_code == 401
def test_create_user_validates_body():
resp = requests.post(
f"{BASE}/users",
json={"name": ""},
headers={"Authorization": "Bearer test-token"}
)
assert resp.status_code == 422
assert "name" in resp.json().get("errors", {})
Expected output: Tests pass if endpoints return correct status codes and response bodies.
const request = require('supertest');
const app = require('./app');
describe('GET /api/users', () => {
it('returns 200 with users array', async () => {
const res = await request(app)
.get('/api/users')
.set('Authorization', 'Bearer test-token');
expect(res.status).toBe(200);
expect(Array.isArray(res.body.users)).toBe(true);
});
it('returns 401 without auth header', async () => {
const res = await request(app).get('/api/users');
expect(res.status).toBe(401);
});
});
Expected output: Jest reports passing and failing test suites with clear error messages.
from locust import HttpUser, task, between
class ApiUser(HttpUser):
wait_time = between(1, 5)
@task
def get_users(self):
self.client.get("/users", headers={"Authorization": "Bearer test-token"})
@task
def create_user(self):
self.client.post("/users", json={"name": "Test User", "email": "test@example.com"},
headers={"Authorization": "Bearer test-token"})
Expected output: Locust reports average response time, requests per second, and error rate under simulated load.
Common Mistakes
1. Only Testing the Happy Path
APIs break most often on edge cases. Test empty inputs, missing fields, invalid types, and duplicate submissions.
2. Ignoring Security Tests
Functional tests that pass can still leave authentication bypass vulnerabilities. Always test unauthorized access.
3. Not Testing Error Responses
Clients depend on error format and status codes. Test that 400, 401, 403, 404, 422, and 500 responses match the spec.
4. Skipping Performance Tests
An API that works for one user may fail at 1000 concurrent users. Load test before every major release.
5. Hardcoding Test Data
Tests that depend on specific database state become fragile. Use factories, fixtures, or API seeding for reproducible tests.
Practice Questions
- What four categories of API testing should every project include?
- Why should you test error responses in addition to success responses?
- What is the difference between functional and contract testing?
- Why is hardcoded test data problematic?
- How does performance testing differ from functional testing?
Answers:
- Functional, security, performance, and contract testing.
- Clients depend on error response formats; undefined errors cause fragile parsing.
- Functional testing validates behavior; contract testing validates the API matches its specification.
- Tests become brittle and fail when seed data changes.
- Performance testing measures response times and throughput under load, not correctness.
Challenge: Write a test suite for a payment API that includes functional tests for creating a charge, security tests for missing auth, and a load test simulating 100 concurrent checkout flows.
FAQ
Mini Project
Build a test suite for a RESTful task management API covering: 5 functional tests (CRUD operations), 3 security tests (unauthenticated access, role-based access), and a locust performance test with 50 concurrent users.
What's Next
Review API monitoring strategies to detect issues in production, or explore API security patterns for preventing common vulnerabilities.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro