API Testing Guide: REST API Testing with Postman, Newman, and Contract Tests
API testing verifies that application programming interfaces function correctly — handling requests, returning expected responses, managing errors, and performing under load — without the complexity of a graphical user interface.
What You’ll Learn
- REST API testing fundamentals: methods, status codes, headers, and bodies
- Creating Postman collections with test scripts for CRUD operations
- Running automated API tests in CI/CD with Newman
- Contract testing with JSON Schema validation
- Error handling and negative testing strategies
Why API Testing Matters
APIs are the backbone of modern applications. A single API failure can break frontend apps, mobile apps, third-party integrations, and internal services. Unlike UI testing, API testing is fast, reliable, and tests the core business logic directly. Most importantly, API tests catch integration issues before they reach the UI layer, where debugging is more expensive.
Durga Antivirus Pro runs thousands of API tests against its threat intelligence API — every signature update, query, and report endpoint is verified before the API is deployed to production.
Learning Path
flowchart LR
A[Testing Basics] --> B[API Testing<br/>You are here]
B --> C[Contract Testing]
C --> D[Performance Testing]
D --> E[CI/CD Integration]
style B fill:#f90,color:#fff
REST API Fundamentals
Before writing tests, understand what you’re testing:
HTTP Methods
| Method | Purpose | Success Code |
|---|---|---|
| GET | Retrieve data | 200 OK |
| POST | Create resource | 201 Created |
| PUT | Replace resource | 200 OK |
| PATCH | Partial update | 200 OK |
| DELETE | Remove resource | 204 No Content |
Status Code Categories
| Range | Meaning | Examples |
|---|---|---|
| 2xx | Success | 200 OK, 201 Created, 204 No Content |
| 3xx | Redirection | 301 Moved, 304 Not Modified |
| 4xx | Client Error | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| 5xx | Server Error | 500 Internal Server Error, 503 Service Unavailable |
Postman Collections
Postman organizes API requests into collections with test scripts written in JavaScript:
Collection Structure
User API
├── Create User (POST /users)
├── Get User (GET /users/:id)
├── Update User (PUT /users/:id)
├── Delete User (DELETE /users/:id)
├── List Users (GET /users)
└── Error Tests
├── Missing Fields (POST /users)
└── Non-Existent User (GET /users/99999)Test Scripts
// POST /users — Create User
pm.test("Status code is 201", () => {
pm.response.to.have.status(201);
});
pm.test("Response has user ID", () => {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property("id");
pm.expect(jsonData.id).to.be.a("number");
});
pm.test("Response matches expected structure", () => {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.all.keys(["id", "name", "email", "createdAt"]);
});
// Save user ID for subsequent tests
pm.environment.set("userId", pm.response.json().id);// GET /users/:id — Get User
pm.test("Status code is 200", () => {
pm.response.to.have.status(200);
});
pm.test("Returns correct user", () => {
const jsonData = pm.response.json();
pm.expect(jsonData.name).to.eql("Alice");
pm.expect(jsonData.email).to.eql("alice@example.com");
});
pm.test("Response time is acceptable", () => {
pm.expect(pm.response.responseTime).to.be.below(500);
});Newman CLI
Newman runs Postman collections from the command line, enabling CI/CD integration:
# Install Newman
npm install -g newman
# Run collection
newman run User-API.postman_collection.json \
--environment User-API.postman_environment.json \
--reporters cli,json \
--reporter-json-export results.jsonCI/CD Integration
# .github/workflows/api-tests.yml
name: API Tests
on: [push, pull_request]
jobs:
api-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Start API
run: docker-compose up -d api
- name: Wait for API
run: |
until curl -s http://localhost:8000/health; do
sleep 2
done
- name: Run API Tests
run: |
npx newman run tests/api/collection.json \
--environment tests/api/env.json \
--reporters cli,junit \
--reporter-junit-export results/junit.xml
- name: Publish Results
uses: dorny/test-reporter@v1
if: always()
with:
name: API Tests
path: results/junit.xml
reporter: java-junitData-Driven Testing with Newman
Use iteration data to test multiple scenarios:
// users-data.json
[
{ "name": "Alice", "email": "alice@test.com", "expectedStatus": 201 },
{ "name": "", "email": "bob@test.com", "expectedStatus": 400 },
{ "name": "Bob", "email": "invalid", "expectedStatus": 400 },
{ "name": "Bob", "email": "", "expectedStatus": 400 },
{ "name": "Charlie", "email": "charlie@test.com", "expectedStatus": 201 }
]newman run collection.json -d users-data.jsonContract Testing with Schema Validation
Contract tests verify that API responses match a defined schema:
// JSON Schema for User object
const userSchema = {
type: "object",
required: ["id", "name", "email", "createdAt"],
properties: {
id: { type: "number" },
name: { type: "string", minLength: 1 },
email: { type: "string", pattern: "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" },
createdAt: { type: "string", format: "date-time" },
},
};
// Postman test
pm.test("Response matches user schema", () => {
const tv4 = require("tv4");
pm.expect(tv4.validate(pm.response.json(), userSchema)).to.be.true;
});Error Testing
Test that the API handles errors gracefully:
// Test validation errors
pm.test("Returns 400 for missing required field", () => {
pm.response.to.have.status(400);
});
pm.test("Error response has message", () => {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property("error");
pm.expect(jsonData.error).to.be.a("string");
});
// Test authentication
pm.test("Returns 401 without auth token", () => {
pm.response.to.have.status(401);
});
// Test not found
pm.test("Returns 404 for unknown resource", () => {
pm.response.to.have.status(404);
});Programmatic Testing with Supertest
For Node.js projects, Supertest provides a programmatic API testing approach:
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
describe('POST /api/users', () => {
it('creates a new user', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@test.com' })
.expect(201);
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe('Alice');
});
it('validates required fields', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice' })
.expect(400);
expect(res.body.error).toContain('email');
});
});
describe('GET /api/users/:id', () => {
it('returns a user by ID', async () => {
const res = await request(app)
.get('/api/users/1')
.expect(200);
expect(res.body.id).toBe(1);
});
it('returns 404 for non-existent user', async () => {
await request(app)
.get('/api/users/99999')
.expect(404);
});
});
});Common API Testing Mistakes
1. Testing Only Happy Paths
Testing only successful API calls misses error handling, validation failures, and edge cases that real users encounter.
Fix: For every endpoint, test: success, validation error, authentication error, not found, and server error.
2. Hardcoded Test Data
Tests that depend on specific data in the database fail when the data changes or the database is refreshed.
Fix: Create test data in setup/teardown (Postman pre-request scripts, test fixtures).
3. Ignoring Response Structure
Checking only the status code misses issues with missing fields, wrong data types, or incorrect formats.
Fix: Validate the full response body, including field types and constraints.
4. No Test Isolation
Tests that depend on other tests’ state (created users, auth tokens) fail when run in isolation.
Fix: Each test should set up its own data and not depend on other tests.
5. Not Testing Performance
API tests that don’t check response times miss performance regressions.
Fix: Add response time assertions. Run performance tests separately for comprehensive analysis.
6. Testing Against Production
Direct API tests against production risk corrupting data and exposing internal endpoints.
Fix: Use staging environments. If production testing is necessary, use read-only endpoints and dedicated test data.
7. No Contract Validation
Without schema validation, API changes that break clients go undetected until integration failures surface.
Fix: Add JSON Schema validation to all API tests. Use contract testing for service-to-service APIs.
Practice Questions
1. What are the four main HTTP methods in REST APIs?
GET (retrieve), POST (create), PUT (replace), DELETE (remove).
2. What is Newman and how is it used?
Newman is the command-line runner for Postman collections. It enables running API tests in CI/CD pipelines.
3. Why should you validate response bodies in addition to status codes?
Status codes don’t guarantee correct response structure. Missing fields, wrong data types, or format changes can break clients even with correct status codes.
4. What is JSON Schema validation and why is it important?
JSON Schema defines the expected structure of a JSON response. It catches contract violations — missing fields, wrong types, constraint violations — before they break consumers.
5. How do you make API tests data-independent?
Create test data in setup (pre-request scripts, before hooks) and clean up in teardown. Don’t assume specific data exists in the database.
Challenge: Create a complete Postman collection for a REST API with at least 5 endpoints. Include schema validation, error tests, authentication tests, and data-driven tests with 3+ data sets. Export and run with Newman in CI.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Contract Testing with Pact | Formal contract testing for microservices |
| Postman Advanced Features | Environments, variables, and collection runner |
| Performance Testing with k6 | Load testing APIs with k6 |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro